国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Python整數(shù)對象池:“內(nèi)存泄漏”?

isLishude / 1912人閱讀

摘要:這里需要說明的是,小的整數(shù)對象,將全部直接放置于內(nèi)存中。內(nèi)存泄漏上述的機制可以很好減輕的問題,同時可以根據(jù)所跑的程序不同的特點來做從而編譯出自己認(rèn)為合適的。

“墻上的斑點”

我第一次注意到短褲上的那個破洞,大概是在金年的三月上旬。如果想要知道具體的時間,那就得回想一下當(dāng)時我看見的東西。我還能夠回憶起,游泳池頂上,搖曳的、白色的燈光不停地映在我的短褲上;有三五名少年一同扎進了水里。哦,那是大概是冬天,因為我回憶起當(dāng)時的前一天我和室友吃了部隊鍋,那段時間我沒有吸煙,反而嚼了許多口香糖,糖紙總是掉下去,無意中埋下頭,這是我第一次看到短褲上的那個破洞。


今天在機場等shuttle時,聽到旁邊的兩個年輕人神采飛揚地討論游泳的話題。莫名地回想起來,幾年前看了一篇講述Python內(nèi)部整數(shù)對象管理機制的文章,其中談到了Python應(yīng)用內(nèi)存池機制來對“大”整數(shù)對象進行管理。從它出發(fā),我想到了一些問題,想要在這里討論一下。

背景

注:本文討論的Python版本是Python 2 (2.7.11),C實現(xiàn)。

“一切皆對象”

我們知道,Python的對象,本質(zhì)上是C中的結(jié)構(gòu)體(生存于在系統(tǒng)堆上)。所有Python對象的根源都是PyObject這個結(jié)構(gòu)體。

打開Python源碼目錄的Include/,可以找到object.h這一文件,這一個文件,是整個Python對象機制的基礎(chǔ)。搜索PyObject,我們將會找到:

typedef struct _object {
    PyObject_HEAD
} PyObject;

再看看PyObject_HEAD這個宏:

#define PyObject_HEAD            
    _PyObject_HEAD_EXTRA        
    Py_ssize_t ob_refcnt;        
    struct _typeobject *ob_type;

在實際編譯出的PyObject中,有ob_refcnt這個變量和ob_type這個指針。前者用于Python的引用計數(shù)垃圾收集,后者用于指定這個對象的“類型對象”。Python中可以把對象分為“普通”對象和類型對象。也就是說,表示對象的類型,是通過一個指針來指向另一個對象,即類型對象,來實現(xiàn)的。這是“一切皆對象”的一個關(guān)鍵體現(xiàn)。

Python中的整數(shù)對象

Python里面,整數(shù)對象的頭文件intobject.h,也可以在Include/目錄里找到,這一文件定義了PyIntObject這一結(jié)構(gòu)體作為Python中的整數(shù)對象:

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

上面提過了,每一個Python對象的ob_type都指向一個類型對象,這里PyIntObject則指向PyInt_Type。想要了解PyInt_Type的相關(guān)信息,我們可以打開intobject.c,并找到如下內(nèi)容:

PyTypeObject PyInt_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "int",
    sizeof(PyIntObject),
    0,
    (destructor)int_dealloc,        /* tp_dealloc */
    (printfunc)int_print,            /* tp_print */
    0,                    /* tp_getattr */
    0,                    /* tp_setattr */
    (cmpfunc)int_compare,            /* tp_compare */
    (reprfunc)int_repr,            /* tp_repr */
    &int_as_number,                /* tp_as_number */
    0,                    /* tp_as_sequence */
    0,                    /* tp_as_mapping */
    (hashfunc)int_hash,            /* tp_hash */
        0,                    /* tp_call */
        (reprfunc)int_repr,            /* tp_str */
    PyObject_GenericGetAttr,        /* tp_getattro */
    0,                    /* tp_setattro */
    0,                    /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
        Py_TPFLAGS_BASETYPE,        /* tp_flags */
    int_doc,                /* tp_doc */
    0,                    /* tp_traverse */
    0,                    /* tp_clear */
    0,                    /* tp_richcompare */
    0,                    /* tp_weaklistoffset */
    0,                    /* tp_iter */
    0,                    /* tp_iternext */
    int_methods,                /* tp_methods */
    0,                    /* tp_members */
    0,                    /* tp_getset */
    0,                    /* tp_base */
    0,                    /* tp_dict */
    0,                    /* tp_descr_get */
    0,                    /* tp_descr_set */
    0,                    /* tp_dictoffset */
    0,                    /* tp_init */
    0,                    /* tp_alloc */
    int_new,                /* tp_new */
    (freefunc)int_free,                   /* tp_free */
};

這里給Python的整數(shù)類型定義了許多的操作。拿int_dealloc,int_freeint_new這幾個操作舉例。顯而易見,int_dealloc負(fù)責(zé)析構(gòu),int_free負(fù)責(zé)釋放該對象所占用的內(nèi)存,int_new負(fù)責(zé)創(chuàng)建新的對象。int_as_number也是比較有意思的一個field。它指向一個PyNumberMethods結(jié)構(gòu)體。PyNumberMethods含有許多個函數(shù)指針,用以定義對數(shù)字的操作,比如加減乘除等等。

通用整數(shù)對象池

Python里面,對象的創(chuàng)建一般是通過Python的C API或者是其類型對象。這里就不詳述具體的創(chuàng)建機制,具體內(nèi)容可以參考Python的有關(guān)文檔。這里我們想要關(guān)注的是,整數(shù)對象是如何存活在系統(tǒng)內(nèi)存中的。

整數(shù)對象大概會是常見Python程序中使用最頻繁的對象了。并且,正如上面提到過的,Python的一切皆對象而且對象都生存在系統(tǒng)的堆上,整數(shù)對象當(dāng)然不例外,那么以整數(shù)對象的使用頻度,系統(tǒng)堆將面臨難以想象的高頻的訪問。一些簡單的循環(huán)和計算,都會致使malloc和free一次次被調(diào)用,由此帶來的開銷是難以計數(shù)的。此外,heap也會有很多的fragmentation的情況,進一步導(dǎo)致性能下降。

這也是為什么通用整數(shù)對象池機制在Python中得到了應(yīng)用。這里需要說明的是,“小”的整數(shù)對象,將全部直接放置于內(nèi)存中。怎么樣定義“小”呢?繼續(xù)看intobject.c,我們可以看到:

#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];

值在這個范圍內(nèi)的整數(shù)對象將被直接換存在內(nèi)存中,small_ints負(fù)責(zé)保存它們的指針。可以理解,這個數(shù)組越大,使用整數(shù)對象的性能(很可能)就越高。但是這里也是一個trade-off,畢竟系統(tǒng)內(nèi)存大小有限,直接緩存的小整數(shù)數(shù)量太多也會影響整體效率。

與小整數(shù)相對的是“大”整數(shù)對象,也就是除開小整數(shù)對象之外的其他整數(shù)對象。既然不可能再緩存所有,或者說大部分常用范圍的整數(shù),那么一個妥協(xié)的辦法就是提供一片空間讓這些大整數(shù)對象按需依次使用。Python也正是這么做的。它維護了兩個單向鏈表block_listfree_list。前者保存了許多被稱為PyIntBlock的結(jié)構(gòu),用于存儲被使用的大整數(shù)的PyIntObject;后者則用于維護前者所有block之中的空閑內(nèi)存。

仍舊是在intobject.c之中,我們可以看到:

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};

typedef struct _intblock PyIntBlock;

一個PyIntBlock保存N_INTOBJECTS個PyIntObject。

現(xiàn)在我們來思考一下一個Python整數(shù)對象在內(nèi)存中的“一生”。

被創(chuàng)建出來之前,先檢查其值的大小,如果在小整數(shù)的范圍內(nèi),則直接使用小整數(shù)池,只用更新其對應(yīng)整數(shù)對象的引用計數(shù)就可以了。如果是大整數(shù),則需要先檢查free_list看是否有空閑的空間,要是沒有則申請新的內(nèi)存空間,更新block_listfree_list,否則就使用free_list指向的下一個空閑內(nèi)存位置并且更新free_list

“內(nèi)存泄漏”?

So far so good. 上述的機制可以很好減輕fragmentation的問題,同時可以根據(jù)所跑的程序不同的特點來做fine tuning從而編譯出自己認(rèn)為合適的Python。但是我們只說了Python整數(shù)對象的“來”還沒有提它的“去”。當(dāng)一個整數(shù)對象的引用計數(shù)變成0以后,會發(fā)生什么事情呢?

小整數(shù)對象自是不必?fù)?dān)心,始終都是在內(nèi)存中的;大整數(shù)對象則需要調(diào)用析構(gòu)操作,int_deallocintobject.c):

static void
int_dealloc(PyIntObject *v)
{
    if (PyInt_CheckExact(v)) {
        Py_TYPE(v) = (struct _typeobject *)free_list;
        free_list = v;
    }
    else
        Py_TYPE(v)->tp_free((PyObject *)v);
}

這個PyInt_CheckExact,來自于intobject.h

#define PyInt_CheckExact(op) ((op)->ob_type == &PyInt_Type)

它起到了類型檢查的作用。所以如果這個指針v指向的不是Python原生整數(shù)對象,則int_dealloc直接調(diào)用該類型的tp_free操作;否則把不再需要的那塊內(nèi)存放入free_list之中。

Py_TYPE的定義:

#ifndef Py_TYPE
    #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
#endif

可以看出,free_list所維護的單向鏈表,是使用ob_type這個field來鏈接前后元素的。

這也就是說,當(dāng)一個大整數(shù)PyIntObject的生命周期結(jié)束時,它之前的內(nèi)存不會交換給系統(tǒng)堆,而是通過free_list繼續(xù)被該Python進程占有。

倘若一個程序使用很多的大整數(shù)呢?倘若每個大整數(shù)只被使用一次呢?是不是很像內(nèi)存泄漏?

我們來做個簡單的計算,假如你的電腦是Macbook Air,8GB Memory,如果你的PyIntObject占用24個Byte,那么滿打滿算,能夠存下大約357913941個整數(shù)對象。

下面做個實驗。以下程序運行在Macbook Pro (mid 2015), 2.5Ghz i7, 16 GB Memory,Python 2.7.11的環(huán)境下:

l = list()
num = 178956971

for i in range(0, num):
    l.append(i)
    if len(l) % 100000 == 0:
        l[:] = []

運行這個程序,會發(fā)現(xiàn)它占用了5.44GB的內(nèi)存:

如果把整數(shù)個數(shù)減半,比如使用89478486,則會占用2.72GB內(nèi)存(正好原來一半):

一個PyIntObject占用多大內(nèi)存呢?

講道理,24 bytes x 178956971 = 4294967304 bytes,約等于2^32,也就是4GB,那么為什么會占用5.44GB呢?

這并非程序其他部分的overhead,因為,就算你的程序只含有:

for i in range(0, 178956971):
    pass

它仍舊會占用5.44GB內(nèi)存。5.44 x 2^30 / 178956971大約等于32.64,也就是均攤下來一個整數(shù)對象占用了32.64個Byte.

這個問題可以作為一個簡單的思考題,這里就不討論了。

總結(jié)

Python的整數(shù)對象管理機制并不復(fù)雜,但也有趣,剛接觸Python的時候是很好的學(xué)習(xí)材料。細(xì)糾下來會發(fā)現(xiàn)有很多工程上的考慮以及與之相關(guān)的現(xiàn)象,值得我們深入挖掘。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/38233.html

相關(guān)文章

  • Python_OOP

    摘要:魔法方法類構(gòu)造方法魔法方法初始化對象創(chuàng)建對象的過程創(chuàng)建一個對象解釋器會自動的調(diào)用方法返回創(chuàng)建的對象的引用,給實例實例化執(zhí)行該方法,返回值。當(dāng)引用計數(shù)為時,該對象生命就結(jié)束了。 define class class的三個組成部分: 類的名稱:類名 類的屬性: 一組數(shù)據(jù) 類的方法:允許對進行操作的方法(行為) 定義 class Student (object): pass...

    tyheist 評論0 收藏0
  • JVM詳解1.Java內(nèi)存模型

    摘要:編譯參見深入理解虛擬機節(jié)走進之一自己編譯源碼內(nèi)存模型運行時數(shù)據(jù)區(qū)域根據(jù)虛擬機規(guī)范的規(guī)定,的內(nèi)存包括以下幾個運運行時數(shù)據(jù)區(qū)域程序計數(shù)器程序計數(shù)器是一塊較小的內(nèi)存空間,他可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。 點擊進入我的博客 1.1 基礎(chǔ)知識 1.1.1 一些基本概念 JDK(Java Development Kit):Java語言、Java虛擬機、Java API類庫JRE(...

    TANKING 評論0 收藏0
  • Android優(yōu)化總結(jié)

    摘要:錯誤使用單利在開發(fā)中單例經(jīng)常需要持有對象,如果持有的對象生命周期與單例生命周期更短時,或?qū)е聼o法被釋放回收,則有可能造成內(nèi)存泄漏。如果集合是類型的話,那內(nèi)存泄漏情況就會更為嚴(yán)重。 目錄介紹 1.OOM和崩潰優(yōu)化 1.1 OOM優(yōu)化 1.2 ANR優(yōu)化 1.3 Crash優(yōu)化 2.內(nèi)存泄漏優(yōu)化 2.0 動畫資源未釋放 2.1 錯誤使用單利 2.2 錯誤使用靜態(tài)變量 2.3 ...

    sunsmell 評論0 收藏0
  • JavaScript 工作原理之三-內(nèi)存管理及如何處理 4 類常見的內(nèi)存泄漏問題(譯)

    摘要:這是因為我們訪問了數(shù)組中不存在的數(shù)組元素它超過了最后一個實際分配到內(nèi)存的數(shù)組元素字節(jié),并且有可能會讀取或者覆寫的位。包含個元素的新數(shù)組由和數(shù)組元素所組成中的內(nèi)存使用中使用分配的內(nèi)存主要指的是內(nèi)存讀寫。 原文請查閱這里,本文有進行刪減,文后增了些經(jīng)驗總結(jié)。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第三章。 我們將會討論日常使用中另一個被開發(fā)...

    weknow619 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<