監控Python中的引用計數

來源:互聯網
上載者:User

問題緣起於python-cn郵件清單的一個問題:http://groups.google.it/group/python-cn/browse_thread/thread/758891b4342eb2d9/92c12bf6acd667ac

有趣的是,為什麼在Python2.4中sys.getrefcount(11111111)的結果是2,到了Python2.5中卻搖身一變,變成了 3?更令人驚奇的是,如果你在Python2.5的IDLE中運行sys.getrefcount(111111),你會驚奇地發現,好了,現在的結果又 變成2了。我們知道sys.getrefcount輸出的是一個對象的引用計數,為什麼相同的代碼,相同的對象,在不同的運行環境中的引用計數卻不同了? 原因是,我們有一個致命的疏漏。

在考察sys.getrefcount時,我們只看到了它的運行結果,但是實際上在啟動並執行背後還有一個更加重要的幕後推手——編譯。為了確認編譯是否對對象的引用計數產生影響,我們來考察幾個例子:

1、互動式環境下:
Python 2.5 (r25:51908, May 27 2007, 09:33:26) [MSC v.1310 32 bit (Inte
32
Type "help", "copyright", "credits" or "license" for more information.

>>> import sys
>>> sys.getrefcount(11111111)
3

2、檔案方式執行 :
[ref.py]
import sys
print sys.getrefcount(11111111)
結果如下:
F:/compile/Python-2.5/PCbuild>python ref.py
3

3、檔案方式執行(避開編譯影響)
[demo.py]
import ref
結果如下:
F:/compile/Python-2.5/PCbuild>python demo.py
2

由此可見,實際上多餘的引用是由Python的編譯過程貢獻的,在1、2兩種執行方式下,Python都會在開始時啟用一個編譯的動作;而在執行方式3下,import機制會導致產生一個ref.pyc檔案,所以不會在每次執行時都會啟用編譯動作。

但是,但是,我們看到在Python2.5的互動式環境中和IDLE環境中,都需要進行編譯,而輸出的結果卻不同,這又是什麼原因呢?嗯,這就需要從Python的源碼中找原因了。我們先來看看sys.getrefcount到底輸出了個什麼玩意兒:
[sysmodule.c]static PyObject *
sys_getrefcount(PyObject *self, PyObject *arg)
...{
    if(arg != NULL && PyInt_Check(arg)) ...{
        if(PyInt_AsLong(arg) == 11111111) ...{
            printf("in sys_getrefcount ");
        }
    }
    return PyInt_FromSsize_t(arg->ob_refcnt);
}

原來輸出的東西就是PyObject中的ob_refcnt值,為了真正搞清楚引用計數的變化情況,我們就來修改Python原始碼,對每一次引用計數的變化進行監控,這需要修改到Python中用於改變引用計數的兩個宏:
[object.h]#define Py_INCREF(op) (                
    robertincref((PyObject*)(op)) ,    
    (op)->ob_refcnt++)

#define Py_DECREF(op)                    
    if (robertdecref((PyObject*)(op)) ,    
    --(op)->ob_refcnt != 0)            
    _Py_CHECK_REFCNT(op)            
    else                        
    _Py_Dealloc((PyObject *)(op))

[object.c]void robertincref(PyObject* obj) ...{
    if(PyInt_Check(obj) && (PyInt_AsLong(obj) == 11111111)) ...{
        long refcnt = obj->ob_refcnt;
        printf("increase ref count from %d to %d ", refcnt, refcnt+1);
    }
}

void robertdecref(PyObject* obj) ...{
    if(PyInt_Check(obj) && (PyInt_AsLong(obj) == 11111111)) ...{
        long refcnt = obj->ob_refcnt;
        printf("decrease ref count from %d to %d ", refcnt, refcnt-1);
    }
}

同時我們還在pyc檔案的讀入點r_object中,整數對象的建立點PyInt_FromLong中,引用計數獲得點sys_getrefcount中添加對11111111的監控代碼,最後的輸出結果如下:

執行方式2(Python2.5互動式環境):
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2   //監視Py_INCREF的結果
decrease ref count from 2 to 1  //監視Py_INCREF的結果
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
LOAD_CONST for 11111111
increase ref count from 2 to 3
in sys_getrefcount //這一行顯示Python虛擬機器目前在sys_getrefcount函數中
decrease ref count from 3 to 2
3  //這個是輸出的結果
decrease ref count from 2 to 1
decrease ref count from 1 to 0

執行方式3(載入pyc檔案):
read 11111111 in r_object
create 11111111 in PyInt_FromLong!
LOAD_CONST for 11111111
increase ref count from 1 to 2
in sys_getrefcount
decrease ref count from 2 to 1
2
decrease ref count from 1 to 0

其中的LOAD_CONST是Python中的一個位元組碼,是sys.getrefcount編譯後的結果,我們在位元組碼指令的實現代碼中也添加了監控代碼。

執行方式2的編譯過程會頻繁地對整數對象進行引用計數的調整;而執行方式3的動作序列則很清晰:
1、Python虛擬機器通過r_object函數從pyc檔案中讀入整數11111111,這會啟用PyInt_FromLong,這裡建立整數對象,並將ob_refcnt設定為1

2、ref.py中的print sys.getrefcount (11111111)最終編譯得到的位元組碼指令中有LOAD_CONST,這條位元組碼指令會通過Py_INCREF增加整數對象的引用計數,這時為2

在執行方式2代表的互動式環境中,編譯過程對對象的引用計數產生了巨大的影響,而在IDLE中,這種影響更為頻繁,我們看一下IDLE方式執行時的輸出結果:
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2
decrease ref count from 2 to 1
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
decrease ref count from 2 to 1
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2
decrease ref count from 2 to 1
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
decrease ref count from 2 to 1
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2
decrease ref count from 2 to 1
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
decrease ref count from 2 to 1
decrease ref count from 1 to 0
decrease ref count from 1 to 0
read 11111111 in r_object
create 11111111 in PyInt_FromLong!
decrease ref count from 1 to 0
LOAD_CONST for 11111111
increase ref count from 1 to 2
in sys_getrefcount
decrease ref count from 2 to 1

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.