前言
OK,对于CPython的整数对象来说,我们前一篇已经导出一个比较明确的立场,那就是小型整数这个设定其实没什么实际用途!你非得要硬杠的话,可能是下面两种做法的人,若是欢迎对号入座。
- 嘿!你的Python应用的高频使用的整数就落在[-5,257)这个区间,OK!你很棒,我无言以对,请自High吧!!
- 嘿!完全可以修改NSMALLPOSINTS和NSMALLNEGINTS这两个宏,并重新编译,以扩充一个你想要的小型整数对象池。
拜托!抱着上面两种想法的人,请将这两种愚蠢想法抛诸脑后吧!尤其抱着第二种想法的,你可能猜不到出自Python编程技术圈内广为推崇的一本中文书籍《Python源代码:深度探索动态语言核心技术》第35页-第36页,居然给予一定的肯定。平心而论,CPython是一种低效的语言,这个是无容置疑的事实,但这里为什么还要花时间去为它写文章,
- 因为CPython易扩展是我最喜欢的原因。只要你想做,都可以用Cython或C去代替Python执行关键的代码逻辑。
- 因为CPython的模块仓库有太多高效的C扩展模块,简直是汪洋大海,当中它们是用C/C++或Cython编写的,值得一提的是Cython写的扩展模块可以非常轻松地跨实现平台迁移至PyPy。
大型整数
走题到此为止,从CPython源代码可知,小型整数完全缓存在small_ints这个数组当中,而超出该区间的整数,CPython运行时会为大型整数直接调用第一层的PyMem函数族为其分配堆内存。
在过去的CPython2.x之后的版本会为大型整数提供专门的缓存池,供大型整数重复使用,而在后续的CPython3.x的PyLongObject就取消了大型整数缓存池.这恰好也说明CPython3.x的整数对象性能低下的原因。
我们不妨回顾一下PyLong_FromLong这个标准的C函数接口,在实例化PyLongObject的函数调用过程中,它会调用_PyLong_New函数,而_PyLong_New函数的底层调用了PyObject_MALLOC函数,进而调用C库底层的malloc函数
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);
}
从上面的代码可知,申请的内存尺寸是ob_digit相对结构体PyLongObject的偏移尺寸加上sizeof(digit)*size类型的尺寸。
对于CPython3.x的内存架构模型,大型整数同时也是大型对象(Big Object),Layer2的所有内存分配是服务于小型对象(Small Object),不要看PyObject_MALLOC好像是第2层的内存函数那样,大型整数的内存分配是实质上跟第2层的内存池对象毫无关系。
整数的行为
我们第一篇在谈论PyTypeObject的时候,以及提及三个重要的字段 分别是tp_as_number、tp_as_sequence、tp_as_mapping。它们分别指向这三个类PyNumberMethod、PySequenceMethods和PyMappingMethods。看一下PyLongType的tp_as_number字段,是一个&long_as_number,顾名思义就是一个PyNumberMethod对象的内存地址。
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类实例化后的一个对象,其具体定义如下代码所示,它是规范PyLongObject行为的预用对象。内部各个字段的指针都是指向和PyLongObject有关整数运算的函数指针。
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实例和类定义的字段顺序是一一对应的吧,注意,这个细节很重要,当你尝试自行修改CPython关于PyLongObject的行为代码(例如,使用自己修订的函数),这些细节都需要注意的。
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内部的各个字段的函数指针所指向的函数都定义在Objects/longobject.c这个文件中,我这里没必要一一列举,请自行按函数名查看源代码。
小结
CPython的PyLongObject的存储形式,请查看回PyLong_FromLong这个函数的算法,从存储形式到内存分配,你会发现为了处理一个整数对象,尤其是大型整数的处理效率都是非常低下的。