前言
OK术辐,對于CPython的整數(shù)對象來說笨使,我們前一篇已經(jīng)導(dǎo)出一個比較明確的立場美澳,那就是小型整數(shù)這個設(shè)定其實沒什么實際用途!你非得要硬杠的話陵究,可能是下面兩種做法的人眠饮,若是歡迎對號入座。
- 嘿铜邮!你的Python應(yīng)用的高頻使用的整數(shù)就落在[-5,257)這個區(qū)間仪召,OK!你很棒松蒜,我無言以對返咱,請自High吧!牍鞠!
- 嘿咖摹!完全可以修改NSMALLPOSINTS和NSMALLNEGINTS這兩個宏,并重新編譯难述,以擴充一個你想要的小型整數(shù)對象池萤晴。
拜托!抱著上面兩種想法的人胁后,請將這兩種愚蠢想法拋諸腦后吧店读!尤其抱著第二種想法的,你可能猜不到出自Python編程技術(shù)圈內(nèi)廣為推崇的一本中文書籍《Python源代碼:深度探索動態(tài)語言核心技術(shù)》第35頁-第36頁攀芯,居然給予一定的肯定屯断。平心而論,CPython是一種低效的語言,這個是無容置疑的事實殖演,但這里為什么還要花時間去為它寫文章氧秘,
- 因為CPython易擴展是我最喜歡的原因。只要你想做趴久,都可以用Cython或C去代替Python執(zhí)行關(guān)鍵的代碼邏輯丸相。
- 因為CPython的模塊倉庫有太多高效的C擴展模塊,簡直是汪洋大海,當中它們是用C/C++或Cython編寫的,值得一提的是Cython寫的擴展模塊可以非常輕松地跨實現(xiàn)平臺遷移至PyPy。
大型整數(shù)
走題到此為止彼棍,從CPython源代碼可知灭忠,小型整數(shù)完全緩存在small_ints這個數(shù)組當中,而超出該區(qū)間的整數(shù)座硕,CPython運行時會為大型整數(shù)直接調(diào)用第一層的PyMem函數(shù)族為其分配堆內(nèi)存弛作。
在過去的CPython2.x之后的版本會為大型整數(shù)提供專門的緩存池,供大型整數(shù)重復(fù)使用华匾,而在后續(xù)的CPython3.x的PyLongObject就取消了大型整數(shù)緩存池.這恰好也說明CPython3.x的整數(shù)對象性能低下的原因映琳。
我們不妨回顧一下PyLong_FromLong這個標準的C函數(shù)接口,在實例化PyLongObject的函數(shù)調(diào)用過程中瘦真,它會調(diào)用_PyLong_New函數(shù)刊头,而_PyLong_New函數(shù)的底層調(diào)用了PyObject_MALLOC函數(shù),進而調(diào)用C庫底層的malloc函數(shù)
PyLongObject *
_PyLong_New(Py_ssize_t size)
{
PyLongObject *result;
/* Number of bytes needed is: offsetof(PyLongObject, ob_digit) +
sizeof(digit)*size. Previous incarnations of this code used
sizeof(PyVarObject) instead of the offsetof, but this risks being
incorrect in the presence of padding between the PyVarObject header
and the digits. */
if (size > (Py_ssize_t)MAX_LONG_DIGITS) {
PyErr_SetString(PyExc_OverflowError,
"too many digits in integer");
return NULL;
}
result = PyObject_MALLOC(offsetof(PyLongObject, ob_digit) +
size*sizeof(digit));
if (!result) {
PyErr_NoMemory();
return NULL;
}
return (PyLongObject*)PyObject_INIT_VAR(result, &PyLong_Type, size);
}
從上面的代碼可知诸尽,申請的內(nèi)存尺寸是ob_digit相對結(jié)構(gòu)體PyLongObject的偏移尺寸加上sizeof(digit)*size類型的尺寸原杂。
對于CPython3.x的內(nèi)存架構(gòu)模型,大型整數(shù)同時也是大型對象(Big Object)您机,Layer2的所有內(nèi)存分配是服務(wù)于小型對象(Small Object),不要看PyObject_MALLOC好像是第2層的內(nèi)存函數(shù)那樣穿肄,大型整數(shù)的內(nèi)存分配是實質(zhì)上跟第2層的內(nèi)存池對象毫無關(guān)系。
整數(shù)的行為
我們第一篇在談?wù)揚yTypeObject的時候际看,以及提及三個重要的字段 分別是tp_as_number咸产、tp_as_sequence、tp_as_mapping仲闽。它們分別指向這三個類PyNumberMethod脑溢、PySequenceMethods和PyMappingMethods±敌溃看一下PyLongType的tp_as_number字段屑彻,是一個&long_as_number,顧名思義就是一個PyNumberMethod對象的內(nèi)存地址顶吮。
PyTypeObject PyLong_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"int", /* tp_name */
offsetof(PyLongObject, ob_digit), /* tp_basicsize */
sizeof(digit), /* tp_itemsize */
....
long_to_decimal_string, /* tp_repr */
&long_as_number, /* tp_as_number */
....
(hashfunc)long_hash, /* tp_hash */
....
PyObject_GenericGetAttr, /* tp_getattro */
....
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */
long_doc, /* tp_doc */
...
long_richcompare, /* tp_richcompare */
....
long_methods, /* tp_methods */
0, /* tp_members */
long_getset, /* tp_getset */
....
long_new, /* tp_new */
PyObject_Del, /* tp_free */
};
long_as_number是 PyNumberMethods類實例化后的一個對象社牲,其具體定義如下代碼所示,它是規(guī)范PyLongObject行為的預(yù)用對象悴了。內(nèi)部各個字段的指針都是指向和PyLongObject有關(guān)整數(shù)運算的函數(shù)指針搏恤。
static PyNumberMethods long_as_number = {
(binaryfunc)long_add, /*nb_add*/
(binaryfunc)long_sub, /*nb_subtract*/
(binaryfunc)long_mul, /*nb_multiply*/
long_mod, /*nb_remainder*/
long_divmod, /*nb_divmod*/
long_pow, /*nb_power*/
(unaryfunc)long_neg, /*nb_negative*/
long_long, /*tp_positive*/
(unaryfunc)long_abs, /*tp_absolute*/
(inquiry)long_bool, /*tp_bool*/
(unaryfunc)long_invert, /*nb_invert*/
long_lshift, /*nb_lshift*/
long_rshift, /*nb_rshift*/
long_and, /*nb_and*/
long_xor, /*nb_xor*/
long_or, /*nb_or*/
long_long, /*nb_int*/
0, /*nb_reserved*/
long_float, /*nb_float*/
....
long_div, /* nb_floor_divide */
long_true_divide, /* nb_true_divide */
....
long_long, /* nb_index */
};
順藤摸瓜地违寿,我們看回PyNumberMethods的類定義,long_as_number這個PyNumberMethods實例和類定義的字段順序是一一對應(yīng)的吧熟空,注意藤巢,這個細節(jié)很重要,當你嘗試自行修改CPython關(guān)于PyLongObject的行為代碼(例如痛阻,使用自己修訂的函數(shù))菌瘪,這些細節(jié)都需要注意的腮敌。
typedef struct {
/* Number implementations must check *both*
arguments for proper type and implement the necessary conversions
in the slot functions themselves. */
binaryfunc nb_add;
binaryfunc nb_subtract;
binaryfunc nb_multiply;
binaryfunc nb_remainder;
binaryfunc nb_divmod;
ternaryfunc nb_power;
unaryfunc nb_negative;
unaryfunc nb_positive;
unaryfunc nb_absolute;
inquiry nb_bool;
unaryfunc nb_invert;
binaryfunc nb_lshift;
binaryfunc nb_rshift;
binaryfunc nb_and;
binaryfunc nb_xor;
binaryfunc nb_or;
unaryfunc nb_int;
void *nb_reserved; /* the slot formerly known as nb_long */
unaryfunc nb_float;
binaryfunc nb_inplace_add;
binaryfunc nb_inplace_subtract;
binaryfunc nb_inplace_multiply;
binaryfunc nb_inplace_remainder;
ternaryfunc nb_inplace_power;
binaryfunc nb_inplace_lshift;
binaryfunc nb_inplace_rshift;
binaryfunc nb_inplace_and;
binaryfunc nb_inplace_xor;
binaryfunc nb_inplace_or;
binaryfunc nb_floor_divide;
binaryfunc nb_true_divide;
binaryfunc nb_inplace_floor_divide;
binaryfunc nb_inplace_true_divide;
unaryfunc nb_index;
binaryfunc nb_matrix_multiply;
binaryfunc nb_inplace_matrix_multiply;
} PyNumberMethods;
long_as_number內(nèi)部的各個字段的函數(shù)指針所指向的函數(shù)都定義在Objects/longobject.c這個文件中阱当,我這里沒必要一一列舉,請自行按函數(shù)名查看源代碼糜工。
小結(jié)
CPython的PyLongObject的存儲形式弊添,請查看回PyLong_FromLong這個函數(shù)的算法,從存儲形式到內(nèi)存分配捌木,你會發(fā)現(xiàn)為了處理一個整數(shù)對象油坝,尤其是大型整數(shù)的處理效率都是非常低下的。