Python源碼剖析筆記2-Python整數(shù)對象
千里之行始于足下睡蟋,從簡單的類別開始分析署咽,由淺入深也不至于自己喪失信心哪痰。先來看看Python整數(shù)對象自点,也就是python中的PyIntObject對象桐罕,對應(yīng)的類型對象是PyInt_Type。
1 Python整數(shù)對象概覽
為了性能考慮桂敛,python中對小整數(shù)有專門的緩存池冈绊,這樣就不需要每次使用小整數(shù)對象時去用malloc分配內(nèi)存以及free釋放內(nèi)存。python2.5.6中默認小整數(shù)的范圍為[-5, 257)埠啃,你也可以修改這個范圍并重新編譯Python死宣。
小整數(shù)有緩存池,那么小整數(shù)之外的大整數(shù)怎么避免重復(fù)分配和回收內(nèi)存呢碴开?Python的方案是PyIntBlock毅该。PyIntBlock這個結(jié)構(gòu)就是一塊內(nèi)存,里面保存PyIntObject對象潦牛。一個PyIntBlock默認存放N_INTOBJECTS對象眶掌,2.5.6版本在32位ubuntu中這個值為82.PyIntBlock鏈表通過block_list維護,每個block中都維護一個PyIntObject數(shù)組objects巴碗,block的objects可能會有些內(nèi)存空閑朴爬,因此需要另外用一個free_list鏈表串起來這些空閑的項以方便再次使用。objects數(shù)組中的PyIntObject對象通過ob_type字段從后往前鏈接橡淆。
小整數(shù)的緩存池最終實現(xiàn)也是生存在block_list維護的內(nèi)存上召噩,在python初始化時母赵,會調(diào)用_PyInt_Init函數(shù)申請內(nèi)存并創(chuàng)建小整數(shù)對象。
下面是一個簡單的數(shù)據(jù)結(jié)構(gòu)圖具滴,表示了這種關(guān)系凹嘲。
2 PyIntObject和PyInt_Type
下面看看PyIntObject和PyInt_Type的定義(在源文件Includes/intobject.h和Objects/intobject.c)。
//先補上PyObject_HEAD
#define PyObject_HEAD \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;
#define PyObject_HEAD_INIT(type) \
1, type,
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
從定義中可以看到构韵,在32位系統(tǒng)中周蹭,一個PyIntObject結(jié)構(gòu)體所占大小為12個字節(jié),這個也可以通過python自帶的模塊來驗證疲恢,運行 import sys; sys.getsizeof(3)
凶朗。
PyInt_Type是PyType_Object類型的對象,它的定義在Objects/intobject.c中显拳。
//PyTypeObject定義在Includes/object.h中
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type) //初始化對象頭部
0, //因為PyTypeObject是個PyVarObject對象俱尼,因此這里需要設(shè)置下大小為0.
"int", //用來打印的字段,比如我們type(3)返回的int字符串就是來自這里萎攒。
sizeof(PyIntObject), //對象基本大小
0, //如果是可變大小對象遇八,這個字段是對象里面存儲項的大小
(destructor)int_dealloc, /* tp_dealloc */
(printfunc)int_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
(cmpfunc)int_compare, /* tp_compare */
(reprfunc)int_repr, /* tp_repr */
&int_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)int_hash, /* tp_hash */
0, /* tp_call */
(reprfunc)int_repr, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE, /* tp_flags */
int_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
int_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
int_new, /* tp_new */
(freefunc)int_free, /* tp_free */
};
//Objects/intobject.c中定義了PyIntObject支持的數(shù)值操作,一共39個耍休。
static PyNumberMethods int_as_number = {
(binaryfunc)int_add, /*nb_add*/
(binaryfunc)int_sub, /*nb_subtract*/
(binaryfunc)int_mul, /*nb_multiply*/
(binaryfunc)int_classic_div, /*nb_divide*/
(binaryfunc)int_mod, /*nb_remainder*/
(binaryfunc)int_divmod, /*nb_divmod*/
...
};
看了PyInt_Type定義刃永,發(fā)現(xiàn)主要就是對象頭部初始化以及對一些函數(shù)的初始化設(shè)置。如int_compare是PyIntObject對象的比較函數(shù)羊精,而int_print是打印PyIntObject的函數(shù)等斯够。此外,還有個很重要的初始化的地方是int_as_number喧锦,這個結(jié)構(gòu)定義了一個對象作為數(shù)值對象時的操作信息读规。如int_add,int_sub等用來執(zhí)行整數(shù)加減燃少。
3 PyIntObject創(chuàng)建和相關(guān)函數(shù)
python中為PyIntObject對象創(chuàng)建主要提供了3個函數(shù)束亏,如下所示。這三個函數(shù)分別從字符串阵具,unicode對象以及l(fā)ong值生成PyIntObject對象碍遍。其中PyInt_FromUnicode最終調(diào)用PyInt_FromString,而PyInt_String最終調(diào)用PyInt_FromLong函數(shù)阳液,因此我這里先簡單分析下PyInt_FromLong函數(shù)怕敬。
//Includes/intobject.h中
(PyObject *) PyInt_FromString(char*, char**, int);
(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
(PyObject *) PyInt_FromLong(long);
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) { //PyIntBlock的objects沒有空閑空間或者第一次分配時,調(diào)用fill_free_list函數(shù)分配PyIntBlock
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)v->ob_type; //更新free_list,指向PyIntBlock的objects的下一個對象
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
這里涉及到小整數(shù)和PyIntBlock相關(guān)內(nèi)容帘皿,在創(chuàng)建PyIntObject對象的時候东跪,會首先判斷數(shù)值大小是否在小整數(shù)范圍內(nèi),如果在,則直接從小整數(shù)對象池small_ints中取虽填。其中small_ints是在python初始化的時候調(diào)用_PyInt_Init函數(shù)創(chuàng)建的丁恭,代碼如下:
#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;
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();
/*通過next指針鏈接PyIntBlock,最新分配的插入到鏈表頭部卤唉,block_list總是指向最新分配的PyIntBlock*/
((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.
將PyIntBlock的objects數(shù)組轉(zhuǎn)換為單向鏈表涩惑,從后往前通過ob_type連接仁期,并返回最后一個對象桑驱。
*/
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
while (--q > p)
q->ob_type = (struct _typeobject *)(q-1);
q->ob_type = NULL;
return p + N_INTOBJECTS - 1;
}
int
_PyInt_Init(void)
{
PyIntObject *v;
int ival;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
if (!free_list && (free_list = fill_free_list()) == NULL)
return 0;
/* PyObject_New is inlined */
v = free_list;
free_list = (PyIntObject *)v->ob_type; // small_ints數(shù)組存儲小整數(shù)對象,注意下small_ints[0]存儲的是-5,small_ints[5]存儲的才是0.free_list從后往前通過ob_type鏈接跛蛋,每次都是從后面取熬的。
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
small_ints[ival + NSMALLNEGINTS] = v;
}
#endif
return 1;
}
static void
int_dealloc(PyIntObject *v)
{
if (PyInt_CheckExact(v)) {
v->ob_type = (struct _typeobject *)free_list;
free_list = v; //修改free_list
}
else
v->ob_type->tp_free((PyObject *)v);
}
4 調(diào)試PyIntObject
按照《Python源碼剖析》中的方法,可以打印出一些調(diào)試信息赊级。先是修改int_print函數(shù)對象押框,來打印小整數(shù)對象和block_list,free_list等相關(guān)信息。修改Objects/intobject.c中的int_print函數(shù)理逊,代碼如下:
static int values[10];
static PyIntObject** addr[10];
static int refcounts[10];
/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)
/* flags -- not used but required by interface */
{
//fprintf(fp, "%ld", v->ob_ival);
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, value=0x%x\n", v, v->ob_ival);
for (i=0; i<10; ++i, --intObjectPtr)
{
values[i] = intObjectPtr->ob_ival;
refcounts[i] = intObjectPtr->ob_refcnt;
addr[i] = intObjectPtr;
}
printf(" value: ");
for (i=0; i < 8; ++i)
{
printf("%d:%p\t", values[i], addr[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;
}
編譯源碼后運行python橡伞,測試的結(jié)果如下:
>>> a = -12345
>>> a
address @0x9415790, value=0xffffcfc7
value: -5:0x9414f80 -4:0x9414f74 -3:0x9414f68 -2:0x9414f5c -1:0x9414f50 0:0x9414f44 1:0x9414f38 2:0x9414f2c
refcnt: 1 1 2 1 32 109 49 30
block_list count: 4
free_list: 0x941579c
>>> b = -12345
>>> b
address @0x941579c, value=0xffffcfc7
value: -5:0x9414f80 -4:0x9414f74 -3:0x9414f68 -2:0x9414f5c -1:0x9414f50 0:0x9414f44 1:0x9414f38 2:0x9414f2c
refcnt: 1 1 2 1 32 109 49 30
block_list count: 4
free_list: 0x94157b4
>>> c = -5
>>> c
address @0x9414f80, value=0xfffffffb
value: -5:0x9414f80 -4:0x9414f74 -3:0x9414f68 -2:0x9414f5c -1:0x9414f50 0:0x9414f44 1:0x9414f38 2:0x9414f2c
refcnt: 5 1 2 1 32 109 49 30
block_list count: 4
free_list: 0x94157b4
>>> d = -5
>>> d
address @0x9414f80, value=0xfffffffb
value: -5:0x9414f80 -4:0x9414f74 -3:0x9414f68 -2:0x9414f5c -1:0x9414f50 0:0x9414f44 1:0x9414f38 2:0x9414f2c
refcnt: 6 1 2 1 32 109 49 30
block_list count: 4
free_list: 0x94157b4
通過測試可以發(fā)現(xiàn),a和b雖然值都是-12345晋被,但是他們是兩個不同的對象兑徘,地址也不一樣。c和d都是-5羡洛,但是由于小整數(shù)緩存機制挂脑,所以c和d的地址是一樣,是同一個對象欲侮。同時我們可以觀察到小整數(shù)中-5到2這8個整數(shù)的地址是從高到低的崭闲,相隔12個字節(jié),這也就驗證了objects數(shù)組是從后往前通過ob_type字段連接成鏈表的威蕉。
另外刁俭,我們可以用id(xxx)來獲取對象的地址(當然這個地址是指邏輯地址),比如上面例子中的id(d)的結(jié)果就0x9414f80韧涨。id對應(yīng)的代碼在Python/bltinmodule.c的builtin_id(PyObject *self, PyObject *v)函數(shù)薄翅,其功能就是打印出python對象的地址。同理氓奈,id(int)就是PyInt_Type類型對象的地址翘魄。
5 總結(jié)
簡單總結(jié)下,Python維護多個PyIntBlock對象舀奶,一個PyIntBlock中存儲多個整數(shù)暑竟。PyIntBlock之間通過鏈表連接,最新分配的PyIntBlock加入在鏈表首部,block_list為鏈表首部。而PyIntBlock中的整數(shù)對象數(shù)組objects通過ob_type指針從后往前鏈接但荤,freelist為該鏈表首部罗岖,即objects數(shù)組的最后一個對象。
整數(shù)對象引用減少到0時腹躁,調(diào)用int_dealloc函數(shù)釋放對象桑包。需要注意的是,PyIntObject釋放對象的時候纺非,并不釋放內(nèi)存哑了,只是將這塊內(nèi)存作為可用內(nèi)存加入到free_list中,并將free_list指向剛剛釋放的對象烧颖。
6 參考資料
陳儒-《python源碼剖析》