Python源碼剖析筆記2-Python整數(shù)對象

Python源碼剖析筆記2-Python整數(shù)對象

千里之行始于足下睡蟋,從簡單的類別開始分析署咽,由淺入深也不至于自己喪失信心哪痰。先來看看Python整數(shù)對象自点,也就是python中的PyIntObject對象桐罕,對應(yīng)的類型對象是PyInt_Type。

1 Python整數(shù)對象概覽

為了性能考慮桂敛,python中對小整數(shù)有專門的緩存池冈绊,這樣就不需要每次使用小整數(shù)對象時去用malloc分配內(nèi)存以及free釋放內(nèi)存。python2.5.6中默認小整數(shù)的范圍為[-5, 257)埠啃,你也可以修改這個范圍并重新編譯Python死宣。

小整數(shù)有緩存池,那么小整數(shù)之外的大整數(shù)怎么避免重復(fù)分配和回收內(nèi)存呢碴开?Python的方案是PyIntBlock毅该。PyIntBlock這個結(jié)構(gòu)就是一塊內(nèi)存,里面保存PyIntObject對象潦牛。一個PyIntBlock默認存放N_INTOBJECTS對象眶掌,2.5.6版本在32位ubuntu中這個值為82.PyIntBlock鏈表通過block_list維護,每個block中都維護一個PyIntObject數(shù)組objects巴碗,block的objects可能會有些內(nèi)存空閑朴爬,因此需要另外用一個free_list鏈表串起來這些空閑的項以方便再次使用。objects數(shù)組中的PyIntObject對象通過ob_type字段從后往前鏈接橡淆。

小整數(shù)的緩存池最終實現(xiàn)也是生存在block_list維護的內(nèi)存上召噩,在python初始化時母赵,會調(diào)用_PyInt_Init函數(shù)申請內(nèi)存并創(chuàng)建小整數(shù)對象。

下面是一個簡單的數(shù)據(jù)結(jié)構(gòu)圖具滴,表示了這種關(guān)系凹嘲。


integer.png

2 PyIntObject和PyInt_Type

下面看看PyIntObject和PyInt_Type的定義(在源文件Includes/intobject.h和Objects/intobject.c)。

//先補上PyObject_HEAD

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

#define PyObject_HEAD_INIT(type)        \
        1, type,

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

從定義中可以看到构韵,在32位系統(tǒng)中周蹭,一個PyIntObject結(jié)構(gòu)體所占大小為12個字節(jié),這個也可以通過python自帶的模塊來驗證疲恢,運行 import sys; sys.getsizeof(3)凶朗。

PyInt_Type是PyType_Object類型的對象,它的定義在Objects/intobject.c中显拳。

//PyTypeObject定義在Includes/object.h中

PyTypeObject PyInt_Type = {
    PyObject_HEAD_INIT(&PyType_Type) //初始化對象頭部
    0,                               //因為PyTypeObject是個PyVarObject對象俱尼,因此這里需要設(shè)置下大小為0.
    "int",                           //用來打印的字段,比如我們type(3)返回的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 */
};

//Objects/intobject.c中定義了PyIntObject支持的數(shù)值操作,一共39個耍休。
static PyNumberMethods int_as_number = {
    (binaryfunc)int_add,    /*nb_add*/
    (binaryfunc)int_sub,    /*nb_subtract*/
    (binaryfunc)int_mul,    /*nb_multiply*/
    (binaryfunc)int_classic_div, /*nb_divide*/
    (binaryfunc)int_mod,    /*nb_remainder*/
    (binaryfunc)int_divmod, /*nb_divmod*/
    ...
};

看了PyInt_Type定義刃永,發(fā)現(xiàn)主要就是對象頭部初始化以及對一些函數(shù)的初始化設(shè)置。如int_compare是PyIntObject對象的比較函數(shù)羊精,而int_print是打印PyIntObject的函數(shù)等斯够。此外,還有個很重要的初始化的地方是int_as_number喧锦,這個結(jié)構(gòu)定義了一個對象作為數(shù)值對象時的操作信息读规。如int_add,int_sub等用來執(zhí)行整數(shù)加減燃少。

3 PyIntObject創(chuàng)建和相關(guān)函數(shù)

python中為PyIntObject對象創(chuàng)建主要提供了3個函數(shù)束亏,如下所示。這三個函數(shù)分別從字符串阵具,unicode對象以及l(fā)ong值生成PyIntObject對象碍遍。其中PyInt_FromUnicode最終調(diào)用PyInt_FromString,而PyInt_String最終調(diào)用PyInt_FromLong函數(shù)阳液,因此我這里先簡單分析下PyInt_FromLong函數(shù)怕敬。

//Includes/intobject.h中
(PyObject *) PyInt_FromString(char*, char**, int);
(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
(PyObject *) PyInt_FromLong(long);   
PyObject *
PyInt_FromLong(long ival)
{
    register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
#ifdef COUNT_ALLOCS
        if (ival >= 0)
            quick_int_allocs++;
        else 
            quick_neg_int_allocs++;
#endif
        return (PyObject *) v;
    }    
#endif
    if (free_list == NULL) { //PyIntBlock的objects沒有空閑空間或者第一次分配時,調(diào)用fill_free_list函數(shù)分配PyIntBlock
        if ((free_list = fill_free_list()) == NULL)                                                                                                            
            return NULL;
    }    
    /* Inline PyObject_New */
    v = free_list;
    free_list = (PyIntObject *)v->ob_type; //更新free_list,指向PyIntBlock的objects的下一個對象
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}

這里涉及到小整數(shù)和PyIntBlock相關(guān)內(nèi)容帘皿,在創(chuàng)建PyIntObject對象的時候东跪,會首先判斷數(shù)值大小是否在小整數(shù)范圍內(nèi),如果在,則直接從小整數(shù)對象池small_ints中取虽填。其中small_ints是在python初始化的時候調(diào)用_PyInt_Init函數(shù)創(chuàng)建的丁恭,代碼如下:

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

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

static PyIntObject *
fill_free_list(void)
{
    PyIntObject *p, *q;
    /* Python's object allocator isn't appropriate for large blocks. */
    p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
    if (p == NULL)
        return (PyIntObject *) PyErr_NoMemory();
     
     /*通過next指針鏈接PyIntBlock,最新分配的插入到鏈表頭部卤唉,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.
       將PyIntBlock的objects數(shù)組轉(zhuǎn)換為單向鏈表涩惑,從后往前通過ob_type連接仁期,并返回最后一個對象桑驱。
        */
    p = &((PyIntBlock *)p)->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p)
        q->ob_type = (struct _typeobject *)(q-1);
    q->ob_type = NULL;
    return p + N_INTOBJECTS - 1;
}

int
_PyInt_Init(void)                                                                                                                                              
{
    PyIntObject *v;
    int ival;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
              if (!free_list && (free_list = fill_free_list()) == NULL)
            return 0;
        /* PyObject_New is inlined */
        v = free_list;
        free_list = (PyIntObject *)v->ob_type; // small_ints數(shù)組存儲小整數(shù)對象,注意下small_ints[0]存儲的是-5,small_ints[5]存儲的才是0.free_list從后往前通過ob_type鏈接跛蛋,每次都是從后面取熬的。
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        small_ints[ival + NSMALLNEGINTS] = v;
    }
#endif
    return 1;
}

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


4 調(diào)試PyIntObject

按照《Python源碼剖析》中的方法,可以打印出一些調(diào)試信息赊级。先是修改int_print函數(shù)對象押框,來打印小整數(shù)對象和block_list,free_list等相關(guān)信息。修改Objects/intobject.c中的int_print函數(shù)理逊,代碼如下:

static int values[10];
static PyIntObject** addr[10];
static int refcounts[10];
/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)
     /* flags -- not used but required by interface */
{
    //fprintf(fp, "%ld", v->ob_ival);
    PyIntObject *intObjectPtr;
    PyIntBlock *p = block_list;
    PyIntBlock *last = NULL;
    int count = 0;
    int i;

    while (p != NULL) 
    {
        ++count;
        last = p;
        p = p->next;
    }

    intObjectPtr = last->objects;
    intObjectPtr += N_INTOBJECTS - 1;
    printf(" address @%p, value=0x%x\n", v, v->ob_ival);

    for (i=0; i<10; ++i, --intObjectPtr)
    {
        values[i] = intObjectPtr->ob_ival;
        refcounts[i] = intObjectPtr->ob_refcnt;
        addr[i] = intObjectPtr;
    }

    printf(" value: ");
    for (i=0; i < 8; ++i)
    {
        printf("%d:%p\t", values[i], addr[i]);
    }
    printf("\n");

    printf(" refcnt: ");
    for (i=0; i < 8; ++i)
    {
        printf("%d\t", refcounts[i]);
    }
    printf("\n");

    printf("block_list count: %d\n", count);
    printf("free_list: %p\n", free_list);

    return 0;
}

編譯源碼后運行python橡伞,測試的結(jié)果如下:

>>> a = -12345
>>> a
 address @0x9415790, value=0xffffcfc7
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 1  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x941579c

>>> b = -12345
>>> b
 address @0x941579c, value=0xffffcfc7
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 1  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x94157b4

>>> c = -5
>>> c
 address @0x9414f80, value=0xfffffffb
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 5  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x94157b4

>>> d = -5
>>> d
 address @0x9414f80, value=0xfffffffb
 value: -5:0x9414f80    -4:0x9414f74    -3:0x9414f68    -2:0x9414f5c    -1:0x9414f50    0:0x9414f44 1:0x9414f38 2:0x9414f2c 
 refcnt: 6  1   2   1   32  109 49  30  
block_list count: 4
free_list: 0x94157b4

通過測試可以發(fā)現(xiàn),a和b雖然值都是-12345晋被,但是他們是兩個不同的對象兑徘,地址也不一樣。c和d都是-5羡洛,但是由于小整數(shù)緩存機制挂脑,所以c和d的地址是一樣,是同一個對象欲侮。同時我們可以觀察到小整數(shù)中-5到2這8個整數(shù)的地址是從高到低的崭闲,相隔12個字節(jié),這也就驗證了objects數(shù)組是從后往前通過ob_type字段連接成鏈表的威蕉。

另外刁俭,我們可以用id(xxx)來獲取對象的地址(當然這個地址是指邏輯地址),比如上面例子中的id(d)的結(jié)果就0x9414f80韧涨。id對應(yīng)的代碼在Python/bltinmodule.c的builtin_id(PyObject *self, PyObject *v)函數(shù)薄翅,其功能就是打印出python對象的地址。同理氓奈,id(int)就是PyInt_Type類型對象的地址翘魄。

5 總結(jié)

簡單總結(jié)下,Python維護多個PyIntBlock對象舀奶,一個PyIntBlock中存儲多個整數(shù)暑竟。PyIntBlock之間通過鏈表連接,最新分配的PyIntBlock加入在鏈表首部,block_list為鏈表首部。而PyIntBlock中的整數(shù)對象數(shù)組objects通過ob_type指針從后往前鏈接但荤,freelist為該鏈表首部罗岖,即objects數(shù)組的最后一個對象。

整數(shù)對象引用減少到0時腹躁,調(diào)用int_dealloc函數(shù)釋放對象桑包。需要注意的是,PyIntObject釋放對象的時候纺非,并不釋放內(nèi)存哑了,只是將這塊內(nèi)存作為可用內(nèi)存加入到free_list中,并將free_list指向剛剛釋放的對象烧颖。

6 參考資料

陳儒-《python源碼剖析》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弱左,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子炕淮,更是在濱河造成了極大的恐慌拆火,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涂圆,死亡現(xiàn)場離奇詭異们镜,居然都是意外死亡,警方通過查閱死者的電腦和手機润歉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門模狭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卡辰,你說我怎么就攤上這事胞皱。” “怎么了九妈?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵反砌,是天一觀的道長。 經(jīng)常有香客問我萌朱,道長宴树,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任晶疼,我火速辦了婚禮酒贬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘翠霍。我一直安慰自己锭吨,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布寒匙。 她就那樣靜靜地躺著零如,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上考蕾,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天祸憋,我揣著相機與錄音,去河邊找鬼肖卧。 笑死蚯窥,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的塞帐。 我是一名探鬼主播拦赠,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼壁榕!你這毒婦竟也來了矛紫?” 一聲冷哼從身側(cè)響起赎瞎,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤牌里,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后务甥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牡辽,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年敞临,在試婚紗的時候發(fā)現(xiàn)自己被綠了态辛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡挺尿,死狀恐怖奏黑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情编矾,我是刑警寧澤熟史,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站窄俏,受9級特大地震影響蹂匹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凹蜈,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一限寞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仰坦,春花似錦履植、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春鼠渺,著一層夾襖步出監(jiān)牢的瞬間鸭巴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工拦盹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鹃祖,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓普舆,卻偏偏與公主長得像恬口,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沼侣,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項目接著寫寫一名3年工作經(jīng)驗的J...
    燕京博士閱讀 7,582評論 1 118
  • 1.元類 1.1.1類也是對象 在大多數(shù)編程語言中祖能,類就是一組用來描述如何生成一個對象的代碼段。在Python中這...
    TENG書閱讀 1,275評論 0 3
  • 整數(shù)對象 PyIntObject PyIntObject 是一個值不可變對象 定義 相應(yīng)的與整數(shù)類型相對應(yīng)的類型對...
    阿布吃de飯閱讀 709評論 0 0
  • 個人筆記蛾洛,方便自己查閱使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik閱讀 67,715評論 0 5
  • “墻上的斑點” 我第一次注意到短褲上的那個破洞轧膘,大概是在金年的三月上旬钞螟。如果想要知道具體的時間,那就得回想一下當時...
    freenik閱讀 784評論 0 4