作者:伯樂線上 – wklken
來源:Python開發者
程式碼我也僅僅是粗粗讀了一遍, 可能出現疏漏和理解錯誤, 發現瞭望指出哈.
示例
>>> a = 1
>>> b = 1
>>> id(a) == id(b)
True
>>> c = 257
>>> d = 257
>>> id(c) == id(d)
False
#在python2.x中, 對於大的序列生成, 建議使用xrange(100000) 而不是range(100000), why?
原始碼位置 Include/intobject.h |
Objects/intobject.c
PyIntObject
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
結構
幾個構造方法
# 從字串, 生成PyIntObject物件
PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int);
# 從Py_UNICODE, 生成PyIntObject物件
#ifdef Py_USING_UNICODE
PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
#endif
# 從long值, 生成PyIntObject物件
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t);
PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t);
這幾個方法, 只需要關註
# 因為大家最後都呼叫這個方法完成物件生成
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
具體的構造方法 PyInt_FromLong
這個方法的定義
PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
/* MARK: 如果, 值在小整數範圍內, 直接從小整數物件池獲取得到物件 */
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
if (–NSMALLNEGINTS ival & ival NSMALLPOSINTS) {
/* MARK: small_ints是什麼後面說 */
v = small_ints[ival + NSMALLNEGINTS];
// 取用+1
Py_INCREF(v);
/* 這裡先忽略, 計數 */
#ifdef COUNT_ALLOCS
if (ival >= 0)
quick_int_allocs++;
else
quick_neg_int_allocs++;
#endif
// 傳回
return (PyObject *) v;
}
#endif
// 如果free_list還不存在, 或者滿了
if (free_list == NULL) {
// 新建一塊PyIntBlock, 並將空閑空間連結串列頭部地址給free_list
if ((free_list = fill_free_list()) == NULL)
// 如果失敗, 傳回
return NULL;
}
// 從free_list分出一個位置存放新的整數
/* Inline PyObject_New */
// 使用單向連結串列頭位置
v = free_list;
// free_list指向單向連結串列下一個位置
free_list = (PyIntObject *)Py_TYPE(v);
// 初始化物件, 型別為PyInt_type, 值為ival
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
// 傳回
return (PyObject *) v;
}
註意這裡的Py_TYPE()方法, 在我們第一篇文章裡面有提到, 不知道的回去複習下物件的資料結構
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
簡而言之:
1. 先判斷數值是否是小整數, 是的話從小整數物件池裡面直接傳回
(這個池固定大小, 下一點講)
2. 如果不是, 從通用整數物件池裡面取一個, 初始化傳回
(如果這時候通用整數物件池還不存在或者已經滿了, 新建一個池加入維護. 通用整數物件池後面講)
小整數物件池
先看定義
#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];
#endif
其實, 小整數物件池就是一個PyIntObject指標陣列(註意是指標陣列), 大小=257+5=262, 範圍是[-5, 257) 註意左閉右開. 即這個陣列包含了262個指向PyIntObject的指標.
結構
建立整數時, 如果在[-5, 257)範圍, 直接傳回已經存在的整數物件指標, 所以我們看到開頭的例子, id比較一個true/一個false
小整數物件池, 在一開始就初始化了, 其初始化程式碼
int
_PyInt_Init(void)
{
PyIntObject *v;
int ival;
// 註意這裡, free_list再次出現
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
// 迴圈, 逐一生成
for (ival = –NSMALLNEGINTS; ival ob_ival = ival;
// 放到陣列裡
small_ints[ival + NSMALLNEGINTS] = v;
}
#endif
return 1;
}
程式碼很眼熟吧, 覺得不眼熟回上面看程式碼
結論
1. 小整數物件池快取 [–5, 257) 內的整數物件, 數值在這個範圍的整數物件有且只存在一個…
2. 小整數物件池, 只是一個指標陣列, 其真正物件依賴通用整數物件池
通用整數物件池1 – 基礎結構PyIntBlock
首先, 有個資料結構PyIntBlock
#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;
回憶一下PyIntObject結構(1個int, 1指標, 1個long), size=4+4+4(先這麼算), N_INTOBJECTS = 82
結構
通用整數物件池2 – 建立過程及執行時結構
有兩個指標
# 指向一個block
static PyIntBlock *block_list = NULL;
# 指向一個PyIntObject
static PyIntObject *free_list = NULL;
生成過程的定義
// 初始化一個PyIntBlock
static PyIntObject *
fill_free_list(void)
{
PyIntObject *p, *q;
// 建立一個新的block
/* Python’s object allocator isn’t appropriate for large blocks. */
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
// 建立失敗(記憶體耗光了)
if (p == NULL)
return (PyIntObject *) PyErr_NoMemory();
// 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. */
// p=block裡面 PyIntObjects陣列頭地址, q是尾地址
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
// 從尾部開始向首部移動, 利用物件裡的ob_type指標(相當於使用這個欄位, ob_type不作為原來的用途), 建立起一個單向連結串列
// 這個單向連結串列的頭部是陣列的最後一個
while (—q > p)
Py_TYPE(q) = (struct _typeobject *)(q–1);
Py_TYPE(q) = NULL; // 單向連結串列最後一個元素的next指向null
// 傳回單向連結串列的頭地址!!!
return p + N_INTOBJECTS – 1;
}
新建第一個時, 只有一個
從裡面拿整數時, 取free_list指向的節點, 然後free_list指向連結串列下一個節點
當一個block用完了之後, 即free_list=NULL, 此時要新建另一個PyIntBlock
新建第二個
通用整數物件池3 – 刪除一個整數時
定義
#define PyInt_CheckExact(op) ((op)->ob_type == &PyInt;_Type)
static void
int_dealloc(PyIntObject *v)
{
// 是整數型別, 將物件放入free_list單向連結串列頭
if (PyInt_CheckExact(v)) {
Py_TYPE(v) = (struct _typeobject *)free_list;
free_list = v;
}
else
Py_TYPE(v)->tp_free((PyObject *)v); //不是整數型別, 對應型別析構
}
可以看到, 回收的時候, 把空間給放回到free_list了, 後面接著用
block_list維護著所有PyIntBlock串列, 檢視原始碼註釋可以看到
PyIntBlocks are never returned to the
system before shutdown (PyInt_Fini).
即, PyIntBlock申請的所有記憶體, 在Python結束之前, 都不會被釋放
所以, 使用range(100000), 執行後, 雖然程式結束了, 但是整數佔用空間還在.
建議對大範圍的序列生成使用xrange
python3.x不用擔心這個問題
《Linux雲端計算及運維架構師高薪實戰班》2018年11月26日即將開課中,120天衝擊Linux運維年薪30萬,改變速約~~~~
*宣告:推送內容及圖片來源於網路,部分內容會有所改動,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。
– END –