Python列表創建與銷毀及緩存池機制
列表的創建
創建列表,Python底層隻提供瞭唯一一個Python/C API,也就是PyList_New。這個函數接收一個size參數,允許我們在創建一個PyListObject對象時指定底層的PyObject *數組的長度。
PyObject * PyList_New(Py_ssize_t size) { //聲明一個PyListObject *對象 PyListObject *op; #ifdef SHOW_ALLOC_COUNT static int initialized = 0; if (!initialized) { Py_AtExit(show_alloc); initialized = 1; } #endif //如果size小於0,直接拋異常 if (size < 0) { PyErr_BadInternalCall(); return NULL; } //緩存池是否可用,如果可用 if (numfree) { //將緩存池內對象個數減1 numfree--; //從緩存池中獲取 op = free_list[numfree]; //設置引用計數 _Py_NewReference((PyObject *)op); #ifdef SHOW_ALLOC_COUNT count_reuse++; #endif } else { //不可用的時候,申請內存 op = PyObject_GC_New(PyListObject, &PyList_Type); if (op == NULL) return NULL; #ifdef SHOW_ALLOC_COUNT count_alloc++; #endif } //如果size等於0,ob_item設置為NULL if (size <= 0) op->ob_item = NULL; else { //否則的話,創建一個指定容量的指針數組,然後讓ob_item指向它 //所以是先創建PyListObject對象, 然後創建指針數組 //最後通過ob_item建立聯系 op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *)); if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); } } //設置ob_size和allocated,然後返回op Py_SIZE(op) = size; op->allocated = size; _PyObject_GC_TRACK(op); return (PyObject *) op; }
我們註意到源碼裡面有一個緩存池,是的,Python大部分對象都有自己的緩存池,隻不過實現的方式不同。
列表的銷毀
創建PyListObject對象時,會先檢測緩存池free_list裡面是否有可用的對象,有的話直接拿來用,否則通過malloc在系統堆上申請。列表的緩存池是使用數組實現的,裡面最多維護80個PyListObject對象。
#ifndef PyList_MAXFREELIST #define PyList_MAXFREELIST 80 #endif static PyListObject *free_list[PyList_MAXFREELIST];
根據之前的經驗我們知道,既然創建的時候能從緩存池中獲取,那麼在執行析構函數的時候也要把列表放到緩存池裡面。
static void list_dealloc(PyListObject *op) { Py_ssize_t i; PyObject_GC_UnTrack(op); Py_TRASHCAN_SAFE_BEGIN(op) //先釋放底層數組 if (op->ob_item != NULL) { i = Py_SIZE(op); //但是釋放之前,還有一件重要的事情 //要將底層數組中每個指針指向的對象的引用計數都減去1 //因為它們不再持有對"對象"的引用 while (--i >= 0) { Py_XDECREF(op->ob_item[i]); } //然後釋放底層數組所占的內存 PyMem_FREE(op->ob_item); } //判斷緩沖池裡面PyListObject對象的個數,如果沒滿,就添加到緩存池 //註意:我們看到執行到這一步的時候, 底層數組已經被釋放掉瞭 if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) //添加到緩存池的時候,是添加到尾部 //獲取的時候也是從尾部獲取 free_list[numfree++] = op; else //否則的話就釋放掉PyListObject對象所占的內存 Py_TYPE(op)->tp_free((PyObject *)op); Py_TRASHCAN_SAFE_END(op) }
我們知道在創建一個新的PyListObject對象時,實際上是分為兩步的,先創建PyListObject對象,然後創建底層數組,最後讓PyListObject對象中的ob_item成員指向這個底層數組。
同理,在銷毀一個PyListObject對象時,先銷毀ob_item維護的底層數組,然後再釋放PyListObject對象自身(如果緩存池已滿)。
現在可以很清晰地明白瞭,原本空蕩蕩的緩存池其實是被已經死去的PyListObject對象填充瞭。在以後創建新的PyListObject對象時,Python會首先喚醒這些死去的PyListObject對象,給它們一個洗心革面、重新做人的機會。但需要註意的是,這裡緩存的僅僅是PyListObject對象,對於底層數組,其ob_item已經不再指向瞭。
從list_dealloc中我們看到,PyListObject對象在放進緩存池之前,ob_item指向的數組就已經被釋放掉瞭,同時數組中指針指向的對象的引用計數會減1。所以最終數組中這些指針指向的對象也大難臨頭各自飛瞭,或生存、或毀滅,總之此時和PyListObject之間已經沒有任何聯系瞭。
但是為什麼要這麼做呢?為什麼不連底層數組也一起維護呢?可以想一下,如果繼續維護的話,數組中指針指向的對象永遠不會被釋放,那麼很可能會產生懸空指針的問題,所以這些指針指向的對象所占的空間必須交還給系統(前提是沒有其它指針指向瞭)。
但是實際上,是可以將PyListObject對象維護的底層數組進行保留的,即:隻將數組中指針指向的對象的引用計數減1,然後將數組中的指針都設置為NULL,不再指向之前的對象瞭,但是並不釋放底層數組本身所占用的內存空間。
因此這樣一來,釋放的內存不會交給系統堆,那麼再次分配的時候,速度會快很多。但是這樣帶來一個問題,就是這些內存沒人用也會一直占著,並且隻能供PyListObject對象的ob_item指向的底層數組使用。因此Python還是為避免消耗過多內存,采取將底層數組所占的內存交還給瞭系統堆這樣的做法,在時間和空間上選擇瞭空間。
lst1 = [1, 2, 3] print(id(lst1)) # 1243303086208 # 扔到緩存池中,放在數組的尾部 del lst1 # 從緩存池中獲取,也會從數組的尾部開始拿 lst2 = [1, 2, 3] print(id(lst2)) # 1243303086208 # 因此打印的地址是一樣的
小結
作為一個功能強大的數據結構,多花些時間是有必要的。
到此這篇關於Python列表創建與銷毀及緩存池機制的文章就介紹到這瞭,更多相關Python 列表內容請搜索LevelAH以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持LevelAH!
推薦閱讀:
- Python帶你從淺入深探究Tuple(基礎篇)
- Python對象的底層實現源碼學習
- Python對象的生命周期源碼學習
- 淺析Python的對象拷貝和內存佈局
- python源碼剖析之PyObject詳解