程式碼我也僅僅是粗粗讀了一遍, 可能出現疏漏和理解錯誤, 發現瞭望指出哈.
今天面了一家靠譜的創業公司, 可惜不是Python向的, 想繼續玩Python是有代價的, 選擇餘地太窄了……
話說寫文章很耗時間, 這個花了兩個多小時….主要還是自個繪圖渣效率低:(
準備找工作事宜很佔時間, 後面只能慢慢來了(好像還很多很多的樣子)
示例
1 2 3 4 5 6 7 8 9 10 11 |
>>> 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
1 2 3 4 |
typedef struct { PyObject_HEAD long ob_ival; } PyIntObject; |
結構
幾個構造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 從字串, 生成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); |
這幾個方法, 只需要關注
1 2 |
# 因為大家最後都呼叫這個方法完成物件生成 PyAPI_FUNC(PyObject *) PyInt_FromLong(long); |
具體的構造方法 PyInt_FromLong
這個方法的定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
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()
方法, 在我們第一篇文章裡面有提到, 不知道的回去複習下物件的資料結構
1 |
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) |
簡而言之:
1 2 3 4 5 |
1. 先判斷數值是否是小整數, 是的話從小整數物件池裡面直接返回 (這個池固定大小, 下一點講) 2. 如果不是, 從通用整數物件池裡面取一個, 初始化返回 (如果這時候通用整數物件池還不存在或者已經滿了, 新建一個池加入維護. 通用整數物件池後面講) |
小整數物件池
先看定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#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
小整數物件池, 在一開始就初始化了, 其初始化程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 2 3 |
1. 小整數物件池快取 [-5, 257) 內的整數物件, 數值在這個範圍的整數物件有且只存在一個... 2. 小整數物件池, 只是一個指標陣列, 其真正物件依賴通用整數物件池 |
通用整數物件池1 – 基礎結構PyIntBlock
首先, 有個資料結構PyIntBlock
1 2 3 4 5 6 7 8 9 10 |
#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 – 建立過程及執行時結構
有兩個指標
1 2 3 4 5 |
# 指向一個block static PyIntBlock *block_list = NULL; # 指向一個PyIntObject static PyIntObject *free_list = NULL; |
生成過程的定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// 初始化一個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 – 刪除一個整數時
定義
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#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
列表, 檢視原始碼註釋可以看到
1 2 |
PyIntBlocks are never returned to the system before shutdown (PyInt_Fini). |
即, PyIntBlock
申請的所有記憶體, 在Python結束之前, 都不會被釋放
1 2 3 4 5 |
所以, 使用range(100000), 執行後, 雖然程式結束了, 但是整數佔用空間還在. 建議對大範圍的序列生成使用xrange python3.x不用擔心這個問題 |
changelog
1 |
2014-08-07 first version |
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式