1. 不可變的PyIntObject "Python源碼剖析 對象初探" 我們對 PyIntObject 已經有了初步的瞭解。 Python 中的對象可以分為固定長度和可變長度兩種類型。除此之外,也可以按照可變和不可變進行劃分。 PyIntObject 則屬於長度固定且不可變的對象。相比其他的對象而 ...
1. 不可變的PyIntObject
Python源碼剖析 - 對象初探 我們對 PyIntObject 已經有了初步的瞭解。 Python 中的對象可以分為固定長度和可變長度兩種類型。除此之外,也可以按照可變和不可變進行劃分。
PyIntObject 則屬於長度固定且不可變的對象。相比其他的對象而言,最簡單,也最容易理解。
我們先來瞭解一下 PyIntObject 類型的類型信息,代碼如下:
PyTypeObject PyInt_Type = {
PyVarObject_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_to_decimal_string, /* 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_to_decimal_string, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_INT_SUBCLASS, /* 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 */
int_getset, /* 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 */
};
核心代碼解釋:
代碼 | 說明 |
---|---|
PyVarObject_HEAD_INIT(&PyType_Type, 0) | 1. 設定ob_type指向PyInt_Type結構的地址 2.定長 |
"int" | 設定 tp_name |
int_dealloc | PyIntObject對象的析構函數 |
int_print | PyIntObject對象的標準輸出函數 |
int_compare | 比較操作 |
int_to_decimal_string | 將整數轉換為數字字元串 |
&int_as_number | 數學操作函數集合,如加減乘除等 |
int_hash | 計算該對象的hash值 |
int_methods | 對象成員函數的集合 |
2. PyIntObject對象創建三種方式
關於 PyIntObject 的對象創建過程,我們在Python源碼剖析 - 對象初探中已經做了初步的介紹,通過閱讀源碼我們可以發現有有以下三種方式,可以用來創建 PyIntObject 對象
PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int);
PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
也就是說,一個 PyIntObject 可以來源於 String、Unicode 和 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) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)Py_TYPE(v);
(void)PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
從代碼實現來看,如果是一個小整數,那麼就直接增加對這個小整數對象的引用,否則,則需要從 free_list 中選取一個可用的對象,並將該對象的 ob_ival 設置為當前的數值。
接下來,我們詳細介紹 PyIntObject 中對小整數的處理方式。
3. PyIntObject的小整數對象
在 Python 中,代碼直接對小整數對象的範圍進行了限定,即 [-5, 257)
#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
這些小整數對象,類似於常量一樣常駐記憶體中,並不會不釋放,這樣做的優點在於:
- 使用效率高,這些小整數對象,像靜態常量一樣,直接拿來就可以用
- 避免經常使用小整數導致記憶體操作效率降低 - 假設我們沒有將小整數常駐記憶體,按照 Python 中其他對象的處理方式來處理,必然會導致 malloc() 和 free() 的頻繁調用,引起大量記憶體碎片,嚴重影響 Python 的整體性
但是這樣做有幾個的問題:
- 常量的設定,是一個經驗值,你沒辦法預計在你的程式里,這個樣的設置就是最優的
- 修改代價大,如果你發現我對小整數的範圍進行調整,你能做的唯一辦法,就是對源碼進行修改,併進行重新編譯。
4. PyIntObject的大整數對象
對於小整數,Python 通過小整數對象池的方式來解決效率問題,那麼對於其他整數對象,又是如何處理的呢。
其實與小整數類似,也是通過記憶體池技術,不同的是這個記憶體池中的數值並不是固定的,而是誰需要使用,就來申請,使用完了,則歸還到池子中去。
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();
((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 = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
while (--q > p)
Py_TYPE(q) = (struct _typeobject *)(q-1);
Py_TYPE(q) = NULL;
return p + N_INTOBJECTS - 1;
}
通過 block_list
和 free_list
兩個指針來進行維護,free_list
是一個單向列表,維護 block_list
中所有可用的記憶體塊。如果 block_list
不夠用了,則調用 fill_free_list()
申請新的記憶體。
5. 更多內容
原文來自兔子先生網站:https://www.xtuz.net/detail-136.html
查看原文 >>> Python源碼剖析 - Python中的整數對象
如果你對Python語言感興趣,可以關註我,或者關註我的微信公眾號:xtuz666