Python 整數(shù)對象的實現(xiàn)

Python 的內建對象存放在源代碼的Objects目錄下。
intobject.c用于整數(shù)對象

在 Python 中,整數(shù)分為小整數(shù)對象大整數(shù)對象

小整數(shù)對象

由于數(shù)值較小的整數(shù)對象在內存中會很頻繁地使用,如果每次都向內存申請空間、請求釋放,會嚴重影響 Python 的性能。好在 整數(shù)對象 屬于不可變對象,可以被共享而不會被修改導致問題,所以為 小整數(shù)對象 劃定一個范圍,即小整數(shù)對象池,在Python運行時初始化并創(chuàng)建范圍內的所有整數(shù),這個范圍內的 整數(shù)對象是被共享的,即一次創(chuàng)建,多次共享引用。

那么這個范圍是多少呢?從源文件中可以看到,而且,用戶可以自行調整,只是每次都要在源文件中修改,而后進行編譯、安裝。

小整數(shù)池的范圍:

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array so that they
   can be shared.
   The integers that are saved are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

從源代碼可以看出
define NSMALLPOSINTS 257,范圍的右邊界
define NSMALLNEGINTS 5,范圍的左邊界
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive),[-5, 257)

大整數(shù)對象

但是,整數(shù)對象很多,不一定都是小整數(shù)對象,又不能將所有整數(shù)對象都放入內存。于是,Python 提供了一個可擴展的內存空間,稱為通用整數(shù)對象池,誰需要用就給誰用,這樣免去了申請空間,又能提高一些效率。

這個空間是一個PyIntBlock結構,是用一個單向列表連接一串內存(block),這個列表由block_list維護,而每個 block 維護一個 整數(shù)對象數(shù)組(Objects),用于存放被緩存的整數(shù)對象。block_list的內容是最新創(chuàng)建的 block。
小整數(shù)對象池 也在block_list上。

Python 使用一個單向鏈表管理全部 block 的 objects 中的所有空閑內存,由free_list指出下一個可用的空閑內存。如果當前沒有空閑內存,free_listNULL,會創(chuàng)建新的內存。

當整數(shù)對象的引用計數(shù)變?yōu)?,會銷毀對象,但并不會釋放空閑出來的內存,即將內存交還系統(tǒng),而是重新加入free_list

hack

使用Xcode修改打印整數(shù)對象的方法

原始文件

/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)
     /* flags -- not used but required by interface */
{
    long int_val = v->ob_ival;
    Py_BEGIN_ALLOW_THREADS
    fprintf(fp, "%ld", int_val);
    Py_END_ALLOW_THREADS
    return 0;
}

修改后,可以打印部分小整數(shù)地址池中,整數(shù)對象的引用次數(shù)、所在內存地址、下一個可用的空閑內存地址

/* ARGSUSED */

static int values[10];
static int refcounts[10];

static int
int_print(PyIntObject *v, FILE *fp, int flags)
     /* flags -- not used but required by interface */
{
    PyIntObject* intObjectPtr;
    PyIntBlock *p = block_list;
    PyIntBlock *last = NULL;
    int count = 0;
    int i;
    
    while(p != NULL)
    {
        ++count;
        last = p;
        p = p->next;
    }
    
    intObjectPtr = last->objects;
    intObjectPtr += N_INTOBJECTS - 1;
    printf(" address @%p\n", v);
    
    for(i = 0; i < 10; ++i, -- intObjectPtr)
    {
        values[i] = intObjectPtr -> ob_ival;
        refcounts[i] = intObjectPtr -> ob_refcnt;
    }
    printf(" value : ");
    for(i = 0; i < 8; ++i)
    {
        printf("%d\t", values[i]);
    }
    printf("\n");
    
    printf(" refcnt : ");
    for(i = 0; i < 8; ++i)
    {
        printf("%d\t", refcounts[i]);
    }
    printf("\n");
    
    printf(" block_list count : %d\n", count);
    printf(" free_list : %p\n", free_list);
    
    return 0;
/*    long int_val = v->ob_ival;
    Py_BEGIN_ALLOW_THREADS
    fprintf(fp, "%ld", int_val);
    Py_END_ALLOW_THREADS
    return 0;*/
}

保存并編譯、安裝,運行修改后的 Python

Python 2.6.9 (unknown, Nov  1 2015, 20:22:05)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> i = -9999
>>> i
 address @0x7fe412f16470       # -9999 所在的內存地址
 value : -5 -4  -3  -2  -1  0   1   2   # 能夠顯示的 -5 ~ 2
 refcnt : 1 1   1   1   35  105 64  41     # 引用計數(shù)器,可以看到,小整數(shù)已經(jīng)被Python自身使用了多次
 block_list count : 8       # block 數(shù)量
 free_list : 0x7fe412f16488     # 下一個可用的空閑內存地址

>>>
>>> a = -258
>>> a
 address @0x7fe412f16488     # -258 的內存地址,是上面`free_list`指出的空閑內存
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 1 1   1   1   35  105 64  41
 block_list count : 8
 free_list : 0x7fe412f164a0    # 新的空閑內存地址

>>> b = -258
>>> b
 address @0x7fe412f164a0    # 上一個的空閑內存地址,可以看出,對于多次創(chuàng)建的大整數(shù)對象,即使值一樣,也是不同的內存地址
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 1 1   1   1   35  105 64  41
 block_list count : 8
 free_list : 0x7fe412f164b8

>>> del b               # 釋放 b 的內存空間
>>> a
 address @0x7fe412f16488
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 1 1   1   1   35  105 64  41
 block_list count : 8
 free_list : 0x7fe412f164a0    # 刪除 b 后,新的空閑內存重新加入`free_list`,沒有歸還給系統(tǒng)

>>> c1 = -5          # 屬于小整數(shù)對象池
>>> c1
 address @0x7fe412f033d8
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 5 1   1   1   35  105 64  41   # -5 引用此時為 5
 block_list count : 8
 free_list : 0x7fe412f164b8

>>> c2 = -5        # 同上
>>> c2
 address @0x7fe412f033d8      # 兩次創(chuàng)建的相同小整數(shù)對象,指向了相同的內存地址
 value : -5 -4  -3  -2  -1  0   1   2
 refcnt : 6 1   1   1   35  105 64  41     # -5 的引用次數(shù)加一,變?yōu)?6
 block_list count : 8
 free_list : 0x7fe412f164b8

>>>

整數(shù)對象的說明文件內置在源代碼中:

PyDoc_STRVAR(int_doc,
"int(x[, base]) -> integer\n\
\n\
Convert a string or number to an integer, if possible.  A floating point\n\
argument will be truncated towards zero (this does not include a string\n\
representation of a floating point number!)  When converting a string, use\n\
the optional base.  It is an error to supply a base when converting a\n\
non-string.  If base is zero, the proper base is guessed based on the\n\
string content.  If the argument is outside the integer range a\n\
long object will be returned instead.");

int_doc就是整數(shù)對象的__doc__屬性

參考資料

《Python 源碼剖析》第二章:整數(shù)對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容