【背景】
原文鏈接: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