深入 Python 整數物件的實現

Namco發表於2015-11-03

本文會深入探究 在Python 內部整數物件是如何實現的。

在 Python 內部,一個整數物件是用 PyIntObject 結構來表示的,該整數物件的值屬性為 long 型。

為了避免每次要用到整數物件的時候都要分配一個新的記憶體,Python 預先為一批尚未使用的空閒整數物件分配了一塊記憶體。

下面這個結構就是 Python 用來給整數物件分配記憶體的,這些整數物件又叫作 PyIntObjects 。該結構初始化完成之後,當 Python 指令碼中為新的物件賦值的時候就可以直接使用之前分配好的整數物件了。這個結構叫做“PyIntBlock”,定義如下所示:

Python 在給一批整數物件分配記憶體塊時,這些物件實際上並沒有被賦值。我們把這些物件叫做“待用的空閒整數物件”。在 Python 程式中,當一個新的整數值被使用時,這個值就會被賦給下一個可用的整數物件。因為空閒整數物件被賦值的過程中不需要記憶體分配,所以速度很快。

在同一個塊內部的這些整數物件是通過叫做“ob_type”的內部指標從後往前倒序連結在一起的。這裡需要注意的是,Python 的原始碼中存在對內部指標的濫用情況,所以對於指標的名字不用太過糾結。

每一個整數塊包含了k個整數物件,k等於 1 KB 的記憶體塊可以容納的整數物件的數目,在我 64 位電腦上大概是 40 個 PyIntObject 物件。當這個塊中所有的整數物件都用完了的時候,就會分配一塊新的記憶體給新的整數物件列表。

已經分配的整數物件記憶體塊是通過一個單向連結串列來記錄的。在Python內部這個列表叫做“block_list”。

在 Python 中使用了一種特殊結構提前為一部分小整數分配了空間,以便快速訪問。這是一個包含 262 個指向整數物件的指標的陣列(在下文會稱之為小整數陣列)。這些小整數物件會在(前面提到的)整數物件塊進行初始化時被分配,它們的範圍是 -5 到 256。許多 Python 程式會頻繁使用這一範圍內的整數,所以這種預處理的辦法非常巧妙。

值為 -5 的整數物件在這個小整數陣列中偏移量為0,也就是說位於陣列的第一個位置,值為 -4 的整數物件偏移量就是 1,以此類推。

試想一下,在 Python 指令碼中定義下面一個整數會發生什麼?

當你執行第一行的時候,就呼叫了 PyInt_FromLong 函式,函式邏輯如下所示:

在我們這個例子中,整數 1 由小整數陣列中第 1 + 5 = 6 個指標所指。函式返回指向該整數物件的指標,然後變數“a”就會指向這個整數物件。

讓我們看一下另外一個例子:

300 並沒有在小整數陣列的範圍內,所以就需要把一個空的整數物件賦值為 300。

如果你看過 Python 2.6 原始碼中的 intobject.c 檔案,你就會看到許多處理相加、相乘、轉換等運算操作的函式。比如說下面這個比較函式:

整數物件的值儲存在物件的 ob_ival 屬性中,型別為 long。每個值都存放在一個暫存器中以優化存取過程,所以比較操作是在兩個暫存器之間完成的。如果 v 指向的整數物件小於 w 指向的整數物件,返回 -1;反之則返回 1。相等的情況下返回 0。

對 Python 中整數的實現就介紹到這裡了。希望你們能喜歡這篇文章並且有所收穫。如果你有什麼想法就在下方留言吧!

相關文章