美文网首页Python中文社区IT狗工作室视觉艺术
第2篇:CPython实现原理:整数对象(后篇)

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

作者: 铁甲万能狗 | 来源:发表于2020-07-07 16:13 被阅读0次

    前言

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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/hmaaqktx.html