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

前言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349