第2篇:CPython實現(xiàn)原理:整數(shù)對象(后篇)

前言

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ù)的處理效率都是非常低下的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刨裆,一起剝皮案震驚了整個濱河市澈圈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帆啃,老刑警劉巖瞬女,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異努潘,居然都是意外死亡诽偷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門疯坤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來报慕,“玉大人,你說我怎么就攤上這事压怠∶吒裕” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵菌瘫,是天一觀的道長蜗顽。 經(jīng)常有香客問我,道長突梦,這世上最難降的妖魔是什么诫舅? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮宫患,結(jié)果婚禮上刊懈,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好虚汛,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布匾浪。 她就那樣靜靜地躺著,像睡著了一般卷哩。 火紅的嫁衣襯著肌膚如雪蛋辈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天将谊,我揣著相機與錄音冷溶,去河邊找鬼。 笑死尊浓,一個胖子當著我的面吹牛逞频,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播栋齿,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼苗胀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瓦堵?” 一聲冷哼從身側(cè)響起基协,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎菇用,沒想到半個月后澜驮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡刨疼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年泉唁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揩慕。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡亭畜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迎卤,到底是詐尸還是另有隱情拴鸵,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布蜗搔,位于F島的核電站劲藐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏樟凄。R本人自食惡果不足惜聘芜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缝龄。 院中可真熱鬧汰现,春花似錦挂谍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嗅战,卻和暖如春妄田,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驮捍。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工疟呐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厌漂。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓萨醒,卻偏偏與公主長得像斟珊,于是被迫代替她去往敵國和親苇倡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344