歡迎光臨
每天分享高質量文章

Python 原始碼閱讀:int

作者:伯樂線上 – wklken

來源:Python開發者

程式碼我也僅僅是粗粗讀了一遍, 可能出現疏漏和理解錯誤, 發現瞭望指出哈.

示例

>>> a = 1

>>> b = 1

>>> id(a) == id(b)

True

 

>>> c = 257

>>> d = 257

>>> id(c) == id(d)

False

 

#在python2.x中, 對於大的序列生成, 建議使用xrange(100000) 而不是range(100000), why?

原始碼位置 Include/intobject.h |

Objects/intobject.c


PyIntObject

typedef struct {

    PyObject_HEAD

    long ob_ival;

} PyIntObject;

結構


幾個構造方法

# 從字串, 生成PyIntObject物件

PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int);

 

# 從Py_UNICODE, 生成PyIntObject物件

#ifdef Py_USING_UNICODE

PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);

#endif

 

# 從long值, 生成PyIntObject物件

PyAPI_FUNC(PyObject *) PyInt_FromLong(long);

 

PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t);

PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t);

這幾個方法, 只需要關註

# 因為大家最後都呼叫這個方法完成物件生成

PyAPI_FUNC(PyObject *) PyInt_FromLong(long);


具體的構造方法 PyInt_FromLong

這個方法的定義

PyObject *

PyInt_FromLong(long ival)

{

    register PyIntObject *v;

 

    /* MARK: 如果, 值在小整數範圍內, 直接從小整數物件池獲取得到物件 */

 

    #if NSMALLNEGINTS + NSMALLPOSINTS > 0

    if (NSMALLNEGINTS  ival & ival  NSMALLPOSINTS) {

 

        /* MARK: small_ints是什麼後面說 */

        v = small_ints[ival + NSMALLNEGINTS];

        // 取用+1

        Py_INCREF(v);

 

        /* 這裡先忽略, 計數 */

        #ifdef COUNT_ALLOCS

            if (ival >= 0)

                quick_int_allocs++;

            else

                quick_neg_int_allocs++;

        #endif

 

        // 傳回

        return (PyObject *) v;

    }

    #endif

 

    // 如果free_list還不存在, 或者滿了

    if (free_list == NULL) {

        // 新建一塊PyIntBlock, 並將空閑空間連結串列頭部地址給free_list

        if ((free_list = fill_free_list()) == NULL)

            // 如果失敗, 傳回

            return NULL;

    }

 

    // 從free_list分出一個位置存放新的整數

 

    /* Inline PyObject_New */

    // 使用單向連結串列頭位置

    v = free_list;

 

    // free_list指向單向連結串列下一個位置

    free_list = (PyIntObject *)Py_TYPE(v);

 

    // 初始化物件, 型別為PyInt_type, 值為ival

    PyObject_INIT(v, &PyInt_Type);

    v->ob_ival = ival;

 

    // 傳回

    return (PyObject *) v;

}

註意這裡的Py_TYPE()方法, 在我們第一篇文章裡面有提到, 不知道的回去複習下物件的資料結構

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

簡而言之:

1. 先判斷數值是否是小整數, 是的話從小整數物件池裡面直接傳回

(這個池固定大小, 下一點講)

 

2. 如果不是, 從通用整數物件池裡面取一個, 初始化傳回

(如果這時候通用整數物件池還不存在或者已經滿了, 新建一個池加入維護. 通用整數物件池後面講)


小整數物件池

先看定義

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

#endif

其實, 小整數物件池就是一個PyIntObject指標陣列(註意是指標陣列), 大小=257+5=262, 範圍是[-5, 257) 註意左閉右開. 即這個陣列包含了262個指向PyIntObject的指標.

結構

建立整數時, 如果在[-5, 257)範圍, 直接傳回已經存在的整數物件指標, 所以我們看到開頭的例子, id比較一個true/一個false

小整數物件池, 在一開始就初始化了, 其初始化程式碼

int

_PyInt_Init(void)

{

    PyIntObject *v;

    int ival;

 

    // 註意這裡, free_list再次出現

 

#if NSMALLNEGINTS + NSMALLPOSINTS > 0

 

    // 迴圈, 逐一生成

    for (ival = –NSMALLNEGINTS; ival ob_ival = ival;

 

        // 放到陣列裡

        small_ints[ival + NSMALLNEGINTS] = v;

    }

#endif

 

    return 1;

}

程式碼很眼熟吧, 覺得不眼熟回上面看程式碼

結論

1. 小整數物件池快取 [5, 257) 內的整數物件, 數值在這個範圍的整數物件有且只存在一個

 

2. 小整數物件池, 只是一個指標陣列, 其真正物件依賴通用整數物件池

通用整數物件池1 – 基礎結構PyIntBlock

首先, 有個資料結構PyIntBlock

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

回憶一下PyIntObject結構(1個int, 1指標, 1個long), size=4+4+4(先這麼算), N_INTOBJECTS = 82

結構

通用整數物件池2 – 建立過程及執行時結構

有兩個指標

# 指向一個block

static PyIntBlock *block_list = NULL;

 

# 指向一個PyIntObject

static PyIntObject *free_list = NULL;

生成過程的定義

// 初始化一個PyIntBlock

static PyIntObject *

fill_free_list(void)

{

    PyIntObject *p, *q;

    // 建立一個新的block

    /* Python’s object allocator isn’t appropriate for large blocks. */

    p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));

 

    // 建立失敗(記憶體耗光了)

    if (p == NULL)

        return (PyIntObject *) PyErr_NoMemory();

 

    // 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. */

 

    // p=block裡面 PyIntObjects陣列頭地址, q是尾地址

    p = &((PyIntBlock *)p)->objects[0];

    q = p + N_INTOBJECTS;

 

    // 從尾部開始向首部移動, 利用物件裡的ob_type指標(相當於使用這個欄位, ob_type不作為原來的用途), 建立起一個單向連結串列

    // 這個單向連結串列的頭部是陣列的最後一個

    while (q > p)

        Py_TYPE(q) = (struct _typeobject *)(q1);

    Py_TYPE(q) = NULL; // 單向連結串列最後一個元素的next指向null

 

    // 傳回單向連結串列的頭地址!!!

    return p + N_INTOBJECTS1;

 

}

新建第一個時, 只有一個

從裡面拿整數時, 取free_list指向的節點, 然後free_list指向連結串列下一個節點

當一個block用完了之後, 即free_list=NULL, 此時要新建另一個PyIntBlock

新建第二個

通用整數物件池3 – 刪除一個整數時

定義

#define PyInt_CheckExact(op) ((op)->ob_type == &PyInt;_Type)

 

static void

int_dealloc(PyIntObject *v)

{

    // 是整數型別, 將物件放入free_list單向連結串列頭

    if (PyInt_CheckExact(v)) {

        Py_TYPE(v) = (struct _typeobject *)free_list;

        free_list = v;

    }

    else

        Py_TYPE(v)->tp_free((PyObject *)v); //不是整數型別, 對應型別析構

}

可以看到, 回收的時候, 把空間給放回到free_list了, 後面接著用

block_list維護著所有PyIntBlock串列, 檢視原始碼註釋可以看到

PyIntBlocks are never returned to the

system before shutdown (PyInt_Fini).

即, PyIntBlock申請的所有記憶體, 在Python結束之前, 都不會被釋放

所以, 使用range(100000), 執行後, 雖然程式結束了, 但是整數佔用空間還在.

 

建議對大範圍的序列生成使用xrange

 

python3.x不用擔心這個問題

《Linux雲端計算及運維架構師高薪實戰班》2018年11月26日即將開課中,120天衝擊Linux運維年薪30萬,改變速約~~~~

    *宣告:推送內容及圖片來源於網路,部分內容會有所改動,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。

    – END –


    贊(0)

    分享創造快樂