天天看點

Python源碼剖析[11] —— PyListObject對象(3)3.      PyListObject對象緩沖池4.      Hack PyListObject

[絕對原創 轉載請注明出處]

Python源碼剖析

——PyListObject對象(3)

本文作者 : Robert Chen ([email protected] )

3.      PyListObject對象緩沖池

還記得嗎,剛才我們按下了一個有趣的話題。沒錯,就是那個緩沖池,free_list。現在,是揭開它的神秘面紗的時候呢。我們想知道的問題是:free_list中所緩沖的PyListObject對象是從哪裡獲得的,是在何時建立的。答案就在一個PyListObject被銷毀的過程中:

[listobject.c]

static void list_dealloc(PyListObject *op)

      
{

      
    int i;

      
    PyObject_GC_UnTrack(op);

      
    Py_TRASHCAN_SAFE_BEGIN(op)

      
    if (op->ob_item != NULL) {

      
        /* Do it backwards, for Christian Tismer.

      
           There's a simple test case where somehow this reduces

      
           thrashing when a *very* large list is created and

      
           immediately deleted. */

      
        i = op->ob_size;

      
        while (--i >= 0) {

      
            Py_XDECREF(op->ob_item[i]);

      
        }

      
        PyMem_FREE(op->ob_item);

      
    }

      
    if (num_free_lists < MAXFREELISTS && PyList_CheckExact(op))

      
        free_lists[num_free_lists++] = op;

      
    else

      
        op->ob_type->tp_free((PyObject *)op);

      
    Py_TRASHCAN_SAFE_END(op)

      
}      

在銷毀一個PyListObject的時候,當然要做的一件事是為list中的每一個元素改變其引用計數。然後,我們就來到了最有趣的部分。Python會檢查我們開始提到的那個緩沖池,free_lists,檢視其中緩存的PyListObject的數量是否已經滿了,如果沒有,就将該待删除的PyListObject放到緩沖池中,以備後用。現在一切真相大白了,那個在Python啟動是空蕩蕩的緩沖池原來都是被本應該死去的PyListObject對象給填充了 ,在以後建立新的PyListObject的時候,Python會首先喚醒這些已經“死去”的PyListObject。感謝黨,感謝政府,又給它們一個重新做“人”的機會:)但是,需要指出,這裡緩沖的僅僅是PyListObject對象,而沒有這個對象曾經擁有的PyObject*清單,因為這些PyObject指針的引用計數已經減少了,這些指針所指的對象都要各奔前程,或生存,或毀滅,不再被PyListObject所給與的那個引用計數所束縛。PyListObject如果繼續維護一個指向這些對象的指針的清單,就可能産生懸空指針的問題。是以,PyObject*清單所占用的空間必須歸還給系統。

看一下我們剛剛建立的PyListObject的最後歸宿:

Python源碼剖析[11] —— PyListObject對象(3)3.      PyListObject對象緩沖池4.      Hack PyListObject

在下一次建立PyListObject時,這個PyListObject将重新被喚醒,重新配置設定PyObject*清單占用的記憶體,重新擁抱新的對象。放眼四周,曾經所擁有過的那些對象,有的已經容顔蒼老,有的已經煙消雲散,是否有一種“無私人非事事休,欲語淚先流”的感慨呢?:)

4.      Hack PyListObject

首先我們來觀察在PyListObject中維護的元素數量變化時,PyListObject中ob_size和allocated兩個變量的變化情況,從中窺見PyListObject對記憶體的使用和管理。

在PyListObject的輸出操作list_print中,我們添加了如下代碼,以觀察PyListObject對記憶體的管理:

printf("/nallocated=%d, ob_size=%d/n", op->allocated, op->ob_size);

觀察結果如圖9所示:

Python源碼剖析[11] —— PyListObject對象(3)3.      PyListObject對象緩沖池4.      Hack PyListObject

    首先建立一個包含一個元素的list,這時ob_size和allocated都是1。這時list中擁有的所有記憶體空間都已被使用完,是以下次插入元素時就一定會調整list的記憶體空間了。

随後向list末尾追加元素2,可以看到,調整記憶體空間的動作發生了。allocated變成了5,而ob_size則變成了2,這裡明确地顯示出了PyListObject所采用的與C++中vector一樣的記憶體緩沖池政策。

繼續向list末尾追加元素3,4,5,當追加了元素5之後,list所擁有的記憶體空間又被使用完了,下一次再追加或插入元素時,記憶體空間調整的動作又會再一次發生。

如果這時在list中删除元素3,可以看到,ob_size發生了變化,而allocated則不發生變化,它始終如一地維護着目前list所擁有的全部記憶體數量。

接下來我們從圖10的結果中觀察一下PyListObject對象的建立和删除對于Python維護的PyListObject對象緩沖池的影響。

Python源碼剖析[11] —— PyListObject對象(3)3.      PyListObject對象緩沖池4.      Hack PyListObject

這次為了消除Python互動環境執行時對PyListObject對象緩沖池的影響,我們通過執行py腳本檔案來觀察,可以看到,當建立新的PyListObject對象時,如果緩沖池中有可用的PyListObject對象,則會使用緩沖池中的對象;而在銷毀一個PyListObject對象時,确實将這個對象放到了緩沖池中。