Python源碼--整數對象(PyIntObject)的內存池

【背景】

原文鏈接:http://blog.csdn.net/ordeder/article/details/25343633

Python整數對象是不可變對象,什麼意思呢?例如執行如下python語句
>>>a = 1023
>>>a = 1024
>>>b = a
>>>c = 1024
>>>d = 195
>>>e = 195

python的整數對象結構爲:
typedef struct {  
    PyObject_HEAD   
    long ob_ival;  
} PyIntObject;

第一條命令執行後,python vm 創建了一個PyIntObject A,其中的ob_ival=1023記錄了該整數對象的值,名字a引用該對象,即A 的 ob_refcnt=1。
當執行第二條語句的時候,python vm 又建立了新的PyIntObject B,其ob_ival值爲1024.且名字a解引用AA的ob_refcnt-1變成0,系統將其回收。名字a引用對象B,B的ob_refcnt=1;
執行第三條語句,名字b引用名字a引用的對象,故而B的ob_refcnt+1,即爲2;
第四條語句:c引用了不同於B的另一個整數對象
第五條語句:d引用了小整數對象195
第六條語句: e和d引用的是同一個對象,及小整數對象
p.s. 小整數的範圍爲[-5,257)

在python中的PyIntObject對象ob_ival內容是不可變的。

【Python中整數對象的存儲優化】

由於python中的整數對象記錄的整數值是不可變的,所以在名字a的值不斷變化的過程中,就就涉及到了多次對象的創建和銷燬。所以python爲整數對象申請空間進行了兩種優化:
優化1:爲通用整數對象存儲池
優化2:爲小整數對象構建特殊的緩衝

        PyIntObject分爲小整數對象[-5~257)及大整數對象。小整數對象在py啓動過程中初始化,從而實現小整數對象的緩存,緩衝中的小整數對象在py運行期間不會被銷燬。        大整數對象需要程序員動態申請,對象在運行過程中根據ob_refcnt引用計數確定是否銷燬(計數爲0)。

       其次,py爲了優化整數對象的申請工作,爲大整數對象引入了緩衝池的概念。爲何引入緩衝池?我的理解是:對於系統來說,alloc一個PyIntObject對象,需要一次系統調用,爲了避免每次創建對象都去調用alloc,便引入整數緩衝池的概念。

【小整數緩衝】

  看着名字感覺挺神奇,其實就是在vm啓動的時候預先將[-5~257)這些整數構建相應的整數對象。這些整數
對象的構建所在的內存空間同樣是在:通用整數對象的緩衝池。只不過這些個小整數對象的ob_refcnt不會改變
且永遠>0,所以在vm運行過程中不會被銷燬,所以起到了緩衝的作用。

【通用整數對象的緩衝池】

       爲了減少alloc系統調用申請空間,內存池一次性申請的空間不是當個PyIntObject大小,而是一個以PyIntBlock塊爲結構的大小的空間,每個PyIntBlock塊容納了n個PyIntObject對象。內存池的基本數據結構如下:

#define BLOCK_SIZE      1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE      8       /* Enough for a 64-bit pointer */
#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;
 系統在啓動的時候,PyIntBlock *block_list爲空的,在運行過程中,如果需要創建整數對象,系統會先判定block_list是否有空閒的空間供創建對象,通過fill_free_list()函數從緩衝池中獲取可用的PyIntObject。
  如果free_list有空閒的PyIntObject可用,則直接在緩衝池中獲取該空閒空間,你懂得。
  如果沒得,系統將通過alloc申請一個PyIntBlock掛入block_list中,同時將該塊分爲N_INTOBJECTS整數對象PyIntObject掛入到free_list中。

1. fill_free_list()的函數實現

static PyIntObject * fill_free_list(void)
{
    PyIntObject *p, *q;
    /* Python's object allocator isn't appropriate for large blocks. */
    p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
    if (p == NULL)
        return (PyIntObject *) PyErr_NoMemory();
    ((PyIntBlock *)p)->next = block_list;
    block_list = (PyIntBlock *)p;
    /* Link the int objects together, from rear to front, then return
       the address of the last int object in the block. */
    p = &((PyIntBlock *)p)->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p)
        Py_TYPE(q) = (struct _typeobject *)(q-1); //[1]
    Py_TYPE(q) = NULL;
    return p + N_INTOBJECTS - 1;
}
說明[1]
py將PyIntObject->ob_type作爲free_list的臨時next指針,使用了指針強制轉換,雖然破壞了指針的安全原則,但是重用了>ob_type內存空間,不失爲一種好方法!下圖描繪了兩個PyIntBlock構成的通用整數緩衝池:


2. 其餘兩個構建和刪除整數對象相關函數:

//構建intobj
PyObject * PyInt_FromLong(long ival)
{
    register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
#ifdef COUNT_ALLOCS
        if (ival >= 0)
            quick_int_allocs++;
        else
            quick_neg_int_allocs++;
#endif
        return (PyObject *) v;
    }
#endif
    if (free_list == NULL) { //[1]
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }
    /* Inline PyObject_New */
    v = free_list;
    //[2]
    free_list = (PyIntObject *)Py_TYPE(v); 
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}
[1]緩衝池的空閒鏈表爲空,通過fill_free_list()去申請新的PyIntBlock
[2](PyIntObject *)Py_TYPE(v)相當於是PyIntObject在free_list中的next指針。

//刪除intobj
static void int_dealloc(PyIntObject *v)
{
    if (PyInt_CheckExact(v)) { //[1]
        Py_TYPE(v) = (struct _typeobject *)free_list;
        free_list = v;
    }
    else //[2]
        Py_TYPE(v)->tp_free((PyObject *)v);
}
[1] 判定如果v的引用計數爲1(經過本次解引用變爲0),則將該PyIntObject空間加入到緩衝池的空閒隊列,以便重用
[2]引用計數>2 將該對象引用計數減1


發佈了55 篇原創文章 · 獲贊 20 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章