Python中文社区IT狗工作室视觉艺术

第2篇:CPython实现原理:整数对象(后篇)

2020-07-07  本文已影响0人  铁甲万能狗

前言

OK,对于CPython的整数对象来说,我们前一篇已经导出一个比较明确的立场,那就是小型整数这个设定其实没什么实际用途!你非得要硬杠的话,可能是下面两种做法的人,若是欢迎对号入座。

拜托!抱着上面两种想法的人,请将这两种愚蠢想法抛诸脑后吧!尤其抱着第二种想法的,你可能猜不到出自Python编程技术圈内广为推崇的一本中文书籍《Python源代码:深度探索动态语言核心技术》第35页-第36页,居然给予一定的肯定。平心而论,CPython是一种低效的语言,这个是无容置疑的事实,但这里为什么还要花时间去为它写文章,

大型整数

走题到此为止,从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这个函数的算法,从存储形式到内存分配,你会发现为了处理一个整数对象,尤其是大型整数的处理效率都是非常低下的。

上一篇下一篇

猜你喜欢

热点阅读