From 16b0f0ccc207ebe9253407c79b6fb685128a4ac9 Mon Sep 17 00:00:00 2001 From: istommao Date: Sun, 26 May 2019 11:22:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20dict-object=20?= =?UTF-8?q?=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- objects/dict-object.md | 240 ++++++++++++++++++++++++----------------- 1 file changed, 142 insertions(+), 98 deletions(-) diff --git a/objects/dict-object.md b/objects/dict-object.md index a7b95b1..f1912d3 100644 --- a/objects/dict-object.md +++ b/objects/dict-object.md @@ -1,11 +1,14 @@ -#### python字典 +# Python字典 Dictionary object implementation using a hash table ,通过描述可知,python的字典就是实现了一个hash表。 -#### Python字典概述 +## Python字典概述 在python的字典中,一个键值对的对应保存就是PyDictEntry类型来保存; +`源文件:`[Include/dict-common.h](https://github.com/python/cpython/blob/v3.7.0/Objects/dict-common.h#L1) + ```c +// Objects/dict-common.h typedef struct { /* Cached hash code of me_key. */ Py_hash_t me_hash; @@ -23,24 +26,27 @@ typedef struct { 4. Pending:索引>=0,键!=空,值=空(仅拆分),尚未插入到拆分表中。 -##### 字典的两种类型 +## 字典的两种类型 python的字典类型中包含了两种联合字典(split-table dictionaries)与分离字典(combined-table dictonaries)。详细的信息可查看有关dict的描述[pep-0412]()。 -###### split-table dictionaries +### split-table dictionaries 当被创建的字典是用来保存object的\_\_dict\_\_属性时,该字典才会创建为一个split-table,它们的健表都被缓存在类型属性中,并且允许所有该类型的实例都可以共享该keys。当出现一个事件讲字典的属性值进行改变的时候,个别字典讲慢慢的转化成组合表的形式。这就保证了在大部分的应用场景下很高的内存利用效率,并保证了在各个场景下的正确性。当split-dict重新改变大小,它会立马改变为一个combined-table,如果重置大小作为保存实例属性的结果,并且只有一个该object的实例,字典会立马再变为一个split-table。如果从split-table中删除一个key, value,它不会删除keys tables中对应的该值,而只是将values数值中移除了该value。 -###### combined-table dictionaries +### combined-table dictionaries 直接通过dict內建函数与{}生成的字典,模块和大部分其他字典都会创建为combined-table字典,一个combined-table不会改变为一个split-table字典,该字典的行为方式与最初的字典的行为方式大致相同。 -##### 容器的相关数据结构 +## 容器的相关数据结构 字典对象是通过PyDictObject来实现数据的,详情如下; -``` +`源文件:`[Include/dictobject.h](https://github.com/python/cpython/blob/v3.7.0/Include/dictobject.h#L17) + +```c +// Include/dictobject.h typedef struct _dictkeysobject PyDictKeysObject; /* The ma_values pointer is NULL for a combined table @@ -69,7 +75,10 @@ typedef struct { 其中,PyDictKeysObject的定义如下; -``` +`源文件:`[Include/dict-common.h](https://github.com/python/cpython/blob/v3.7.0/Objects/dict-common.h#L20) + +```c +// Objects/dict-common.h /* See dictobject.c for actual layout of DictKeysObject */ struct _dictkeysobject { Py_ssize_t dk_refcnt;                  // 引用计数 @@ -121,11 +130,11 @@ struct _dictkeysobject { 相关数据结构的内存布局为; ![python_dict_mem](./python_dict_mem.png) -#### Python字典示例 +## Python字典示例 本次示例脚本如下: -``` +```python d = {} d['1']='2' d['1']='e' @@ -135,13 +144,13 @@ d.pop('1') 通过Python的反汇编工具获取字节码; -``` +```shell python -m dis dict_test.py ``` 输出的字节码如下; -``` +```shell 2 0 BUILD_MAP 0 2 STORE_NAME 0 (d) @@ -166,39 +175,49 @@ python -m dis dict_test.py 通过字节码指令可知,首先调用了BUILD_MAP来创建一个新的字典,接着就对新建的字典d进行了赋值操作与更新操作,最后调用了pop方法删除一个key。接下来就详细分析一下相关流程。 -##### 字典的初始化流程 +## 字典的初始化流程 通过查找BUILD_MAP的虚拟机执行函数; -``` - TARGET(BUILD_MAP) { - Py_ssize_t i; - PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg); // 新建并初始化一个字典 - if (map == NULL) - goto error;  // 如果新建失败则报错 - for (i = oparg; i > 0; i--) {   // 检查在新建的过程中是否通过参数传值 - int err; - PyObject *key = PEEK(2*i); - PyObject *value = PEEK(2*i - 1); - err = PyDict_SetItem(map, key, value);      // 找到对应的值并讲该值设置到map中 - if (err != 0) {                        // 检查是否报错 - Py_DECREF(map); - goto error;                        // 如果错误就报错处理 - } - } +`源文件:`[Python/ceval.c](https://github.com/python/cpython/blob/v3.7.0/Python/ceval.c#L2357) - while (oparg--) { - Py_DECREF(POP());                       // 弹出栈上输入参数的引用 - Py_DECREF(POP()); +```c +// Python/ceval.c +switch (opcode) { + ... + + TARGET(BUILD_MAP) { + Py_ssize_t i; + PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg); // 新建并初始化一个字典 + if (map == NULL) + goto error;  // 如果新建失败则报错 + for (i = oparg; i > 0; i--) {   // 检查在新建的过程中是否通过参数传值 + int err; + PyObject *key = PEEK(2*i); + PyObject *value = PEEK(2*i - 1); + err = PyDict_SetItem(map, key, value);      // 找到对应的值并讲该值设置到map中 + if (err != 0) {                        // 检查是否报错 + Py_DECREF(map); + goto error;                        // 如果错误就报错处理 } - PUSH(map);                              // 讲生成的map压栈 - DISPATCH();                             // 检查是否需要执行下一条字节码指令 } + + while (oparg--) { + Py_DECREF(POP());                       // 弹出栈上输入参数的引用 + Py_DECREF(POP()); + } + PUSH(map);                              // 讲生成的map压栈 + DISPATCH();                             // 检查是否需要执行下一条字节码指令 + } +} ``` 从该函数的执行可知,初始化的函数是从_PyDict_NewPresized开始,该函数就是生成并初始化一个字典; -``` +`源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L1240) + +```c +// Objects/dictobject.c PyObject * _PyDict_NewPresized(Py_ssize_t minused) @@ -232,7 +251,10 @@ _PyDict_NewPresized(Py_ssize_t minused) 首先,先计算出需要生成的字典的大小,然后再初始化一个PyDictKeysObject,最后就生成一个PyDictObject返回。继续查看new_keys_object的执行流程; -``` +`源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L503) + +```c +// Objects/dictobject.c static PyDictKeysObject *new_keys_object(Py_ssize_t size) { @@ -283,7 +305,11 @@ static PyDictKeysObject *new_keys_object(Py_ssize_t size) 主要就是通过传入的size,检查是否超过设置的大小,检查是否有缓存的字典数据可用,如果没有则申请内存重新生成一个dk,最后进行申请到的内存讲内容清空。接着就会进行new_dict初始化数据; -``` +`源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L568) + +```c +// Objects/dictobject.c + /* Consumes a reference to the keys object */ static PyObject * new_dict(PyDictKeysObject *keys, PyObject **values) @@ -315,33 +341,41 @@ new_dict(PyDictKeysObject *keys, PyObject **values) new_dict就是根据keys,values设置到从缓冲池或者新生成一个dict对象,最后返回。至此,dict的创建工作已经完成。 -##### 字典的插入与查找 +## 字典的插入与查找 通过字节码的指令STORE_SUBSCR可知,该命令就是讲'1'作为key, '2'作为value插入到d中,此时查看该执行函数; -``` +`源文件:`[Python/ceval.c](https://github.com/python/cpython/blob/v3.7.0/Python/ceval.c#L1561) - TARGET(STORE_SUBSCR) { - PyObject *sub = TOP(); // 第一个值为key - PyObject *container = SECOND(); // 该为字典对象 - PyObject *v = THIRD(); // 该为value - int err; - STACKADJ(-3); - /* container[sub] = v */ - err = PyObject_SetItem(container, sub, v); // 调用该方法设置值 - Py_DECREF(v); - Py_DECREF(container); - Py_DECREF(sub); - if (err != 0) - goto error; - DISPATCH(); - +```c +// Python/ceval.c +switch (opcode) { + ... + + TARGET(STORE_SUBSCR) { + PyObject *sub = TOP(); // 第一个值为key + PyObject *container = SECOND(); // 该为字典对象 + PyObject *v = THIRD(); // 该为value + int err; + STACKADJ(-3); + /* container[sub] = v */ + err = PyObject_SetItem(container, sub, v); // 调用该方法设置值 + Py_DECREF(v); + Py_DECREF(container); + Py_DECREF(sub); + if (err != 0) + goto error; + DISPATCH(); + } +} ``` 此时,从栈中取出相关参数,并将这些值传入PyObject_SetItem函数进行处理设置值; -``` +`源文件:`[Objects/abstract.c](https://github.com/python/cpython/blob/v3.7.0/Objects/abstract.c#L186) +```c +// Objects/abstract.c int PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value) { @@ -377,7 +411,10 @@ PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value) 其中就调用了字典的tp_as_mapping的方法集,并调用了该方法集的mp_ass_subscript方法;此时我们分析一下,dict的tp_as_mapping的方法集。此时就调用了tp_as_mapping的mp_ass_subscript方法,此时就是调用dict的dict_ass_sub方法; -``` +`源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L2040) + +```c +// Objects/dictobject.c static int dict_ass_sub(PyDictObject *mp, PyObject *v, PyObject *w) { @@ -390,7 +427,10 @@ dict_ass_sub(PyDictObject *mp, PyObject *v, PyObject *w) 可知,删除一个key就是PyDict_DelItem,设置一个key就是PyDict_SetItem; -``` +`源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L1433) + +```c +// Objects/dictobject.c int PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) { @@ -419,7 +459,10 @@ PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) insertdict方法就是将生成的方法,插入到字典中去; -``` +`源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L987) + +```c +// Objects/dictobject.c static int insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) { @@ -510,7 +553,11 @@ Fail: 其中dk_lookup对应的方法,在初始化之后对应的是lookdict_unicode_nodummy; -``` +`源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L813) + +```c +// Objects/dictobject.c + /* Faster version of lookdict_unicode when it is known that no keys * will be present. */ static Py_ssize_t _Py_HOT_FUNCTION @@ -557,11 +604,11 @@ lookdict_unicode_nodummy(PyDictObject *mp, PyObject *key, 该函数的主要工作就是查找,字典中是否有空余的值,或者如果找到了满足hash值与key相同的就将value设置为找到的值(这也是字典查找的核心逻辑)。至此,字典的插入的大致流程已经分析完毕。 -#### Python字典的操作测试 +## Python字典的操作测试 现在我们动手观看一下具体的操作实例,首先声明,该例子仅供调试使用,目前调试的字典的key与value都是float类型并且不能del或者pop其中的key。操作字典如下所示; -``` +```python d = {20000:2} d[1] = 2 d[3] = 2 @@ -569,42 +616,42 @@ d[3] = 2 首先,讲如下代码插入到dictobject.c的1060行; -``` - // 测试代码 - PyObject* key1 = PyLong_FromLong(20000); - Py_hash_t hash1 = PyObject_Hash(key1); - PyObject* old_value1; - Py_ssize_t ix1 = mp->ma_keys->dk_lookup(mp, key1, hash1, &old_value1); - if (ix1 == 0){ - PyLongObject* give; - give = (PyLongObject* )key1; - printf("found value : %ld\n", give->ob_digit[0]); - PyDictKeyEntry *ep01 = DK_ENTRIES(mp->ma_keys); - int i, count; - count = mp->ma_used; - int size_count, j; - size_count = mp->ma_keys->dk_size; - printf("%s ", mp->ma_keys->dk_indices); - int8_t *indices = (int8_t*)(mp->ma_keys->dk_indices); - printf("indices index values :"); - for (j=0; jme_key; - printf("size : %d ", mp->ma_keys->dk_size); - printf("found value while  key : %ld ", give->ob_digit[0]); - give = (PyLongObject* )ep01->me_value; - printf("value : %ld\n", give->ob_digit[0]); - ep01++; - } - } +```c +// 测试代码 +PyObject* key1 = PyLong_FromLong(20000); +Py_hash_t hash1 = PyObject_Hash(key1); +PyObject* old_value1; +Py_ssize_t ix1 = mp->ma_keys->dk_lookup(mp, key1, hash1, &old_value1); +if (ix1 == 0){ + PyLongObject* give; + give = (PyLongObject* )key1; + printf("found value : %ld\n", give->ob_digit[0]); + PyDictKeyEntry *ep01 = DK_ENTRIES(mp->ma_keys); + int i, count; + count = mp->ma_used; + int size_count, j; + size_count = mp->ma_keys->dk_size; + printf("%s ", mp->ma_keys->dk_indices); + int8_t *indices = (int8_t*)(mp->ma_keys->dk_indices); + printf("indices index values :"); + for (j=0; jme_key; + printf("size : %d ", mp->ma_keys->dk_size); + printf("found value while  key : %ld ", give->ob_digit[0]); + give = (PyLongObject* )ep01->me_value; + printf("value : %ld\n", give->ob_digit[0]); + ep01++; + } +} ``` 然后编译运行; -``` +```python Python 3.7.3 (default, May 22 2019, 16:17:57) [GCC 7.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. @@ -616,7 +663,7 @@ size : 8 found value while  key : 20000 value : 2 其中为什么初始化的时候输入20000,是根据代码找到相关的key值,因为字典也被python自身实现的结构中引用了多次,所以我们就设置了一个特殊值来跟踪我们想要的字典;当d初始化的时候,就输出如上所示内容;我们接下来继续操作; -``` +```python >>> d = {20000:2} found value : 20000 indices index values :0 -1 -1 -1 -1 -1 -1 -1 @@ -652,7 +699,7 @@ size : 8 found value while  key : 7 value : 8 此后我们一直添加值进d,从输出信息可知,index就是记录了PyDictKeyEntry的索引值,-1就表示该处未使用。 当我们继续向d中添加内容时; -``` +```python >>> d[9] = 10 found value : 20000 indices index values :0 -1 1 2 -1 3 -1 4 -1 5 -1 -1 -1 -1 -1 -1 @@ -675,6 +722,3 @@ size : 16 found value while  key : 10 value : 11 ``` 从输出内容可知,字典的大小随之改变了,这也说明了python字典的最佳大小容量限定在1/2到2/3之间,如果超过这个阈值则字典就会自动扩容,扩容的策略大家可详细查看源码。 - - - From f189c17209dbb90c8b968e403cce1f8d72f62e15 Mon Sep 17 00:00:00 2001 From: istommao Date: Sun, 26 May 2019 21:21:03 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=95=B4=E6=95=B0?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E7=9A=84=E5=86=85=E5=AE=B9=EF=BC=8C=E6=95=B4?= =?UTF-8?q?=E6=95=B0=E7=9B=B8=E5=8A=A0=E4=B8=8E=E7=9B=B8=E4=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- objects/long-object.md | 231 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 3 deletions(-) diff --git a/objects/long-object.md b/objects/long-object.md index a2972f1..1ffed04 100644 --- a/objects/long-object.md +++ b/objects/long-object.md @@ -43,8 +43,6 @@ typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */ The allocation function takes care of allocating extra memory so that ob_digit[0] ... ob_digit[abs(ob_size)-1] are actually available. - 内存分配函数要小心额外的内存, - CAUTION: Generic code manipulating subtypes of PyVarObject has to aware that ints abuse ob_size's sign bit. @@ -239,7 +237,6 @@ _PyLong_Init(void) } ``` - ## 整数的存储结构 `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L1581) @@ -293,3 +290,231 @@ print(num) ![longobject storage](longobject_storage.png) 注:这里的 30 是由 **PyLong_SHIFT** 决定的,64位系统中,**PyLong_SHIFT** 为30,否则 **PyLong_SHIFT** 为15 + +## 整数对象的数值操作 + +可以看到整数对象的数值操作较多,由于篇幅限制无法一一分析,这里只分析整数的部分方法 + +`源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5341) + +```c +// Objects/longobject.c + +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 */ + (unaryfunc)long_long, /*tp_positive */ + (unaryfunc)long_abs, /*tp_absolute 绝对值 */ + (inquiry)long_bool, /*tp_bool 求bool值 */ + (unaryfunc)long_invert, /*nb_invert 反转 */ + long_lshift, /*nb_lshift 逻辑左移 */ + (binaryfunc)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*/ + 0, /* nb_inplace_add */ + 0, /* nb_inplace_subtract */ + 0, /* nb_inplace_multiply */ + 0, /* nb_inplace_remainder */ + 0, /* nb_inplace_power */ + 0, /* nb_inplace_lshift */ + 0, /* nb_inplace_rshift */ + 0, /* nb_inplace_and */ + 0, /* nb_inplace_xor */ + 0, /* nb_inplace_or */ + long_div, /* nb_floor_divide */ + long_true_divide, /* nb_true_divide */ + 0, /* nb_inplace_floor_divide */ + 0, /* nb_inplace_true_divide */ + long_long, /* nb_index */ +}; +``` + +### 整数相加 + +`源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L2990) + +```c +// Objects/longobject.c + +/* Add the absolute values of two integers. */ + +static PyLongObject * +x_add(PyLongObject *a, PyLongObject *b) +{ + Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); + PyLongObject *z; + Py_ssize_t i; + digit carry = 0; + + /* Ensure a is the larger of the two: */ + // 确保 a 大于 b + if (size_a < size_b) { + { PyLongObject *temp = a; a = b; b = temp; } + { Py_ssize_t size_temp = size_a; + size_a = size_b; + size_b = size_temp; } + } + z = _PyLong_New(size_a+1); + if (z == NULL) + return NULL; + for (i = 0; i < size_b; ++i) { + carry += a->ob_digit[i] + b->ob_digit[i]; + z->ob_digit[i] = carry & PyLong_MASK; + carry >>= PyLong_SHIFT; + } + for (; i < size_a; ++i) { + carry += a->ob_digit[i]; + z->ob_digit[i] = carry & PyLong_MASK; + carry >>= PyLong_SHIFT; + } + z->ob_digit[i] = carry; + return long_normalize(z); +} + +/* Subtract the absolute values of two integers. */ + +static PyLongObject * +x_sub(PyLongObject *a, PyLongObject *b) +{ + Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); + PyLongObject *z; + Py_ssize_t i; + int sign = 1; + digit borrow = 0; + + /* Ensure a is the larger of the two: */ + // 确保 a 大于 b + if (size_a < size_b) { + sign = -1; + { PyLongObject *temp = a; a = b; b = temp; } + { Py_ssize_t size_temp = size_a; + size_a = size_b; + size_b = size_temp; } + } + else if (size_a == size_b) { + /* Find highest digit where a and b differ: */ + // 找到最高位 a 与 b的差异 + i = size_a; + while (--i >= 0 && a->ob_digit[i] == b->ob_digit[i]) + ; + if (i < 0) + return (PyLongObject *)PyLong_FromLong(0); + if (a->ob_digit[i] < b->ob_digit[i]) { + sign = -1; + { PyLongObject *temp = a; a = b; b = temp; } + } + size_a = size_b = i+1; + } + z = _PyLong_New(size_a); + if (z == NULL) + return NULL; + for (i = 0; i < size_b; ++i) { + /* The following assumes unsigned arithmetic + works module 2**N for some N>PyLong_SHIFT. */ + borrow = a->ob_digit[i] - b->ob_digit[i] - borrow; + z->ob_digit[i] = borrow & PyLong_MASK; + borrow >>= PyLong_SHIFT; + borrow &= 1; /* Keep only one sign bit 只保留一位符号位 */ + } + for (; i < size_a; ++i) { + borrow = a->ob_digit[i] - borrow; + z->ob_digit[i] = borrow & PyLong_MASK; + borrow >>= PyLong_SHIFT; + borrow &= 1; /* Keep only one sign bit */ + } + assert(borrow == 0); + if (sign < 0) { + Py_SIZE(z) = -Py_SIZE(z); + } + return long_normalize(z); +} + +static PyObject * +long_add(PyLongObject *a, PyLongObject *b) +{ + PyLongObject *z; + + CHECK_BINOP(a, b); + + if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) { + return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b)); + } + if (Py_SIZE(a) < 0) { + if (Py_SIZE(b) < 0) { + z = x_add(a, b); + if (z != NULL) { + /* x_add received at least one multiple-digit int, + and thus z must be a multiple-digit int. + That also means z is not an element of + small_ints, so negating it in-place is safe. */ + assert(Py_REFCNT(z) == 1); + Py_SIZE(z) = -(Py_SIZE(z)); + } + } + else + z = x_sub(b, a); + } + else { + if (Py_SIZE(b) < 0) + z = x_sub(a, b); + else + z = x_add(a, b); + } + return (PyObject *)z; +} +``` + +可以看到整数的加法运算函数long_add根据 a、b的ob_size 又细分为两个函数做处理 x_add 和 x_sub + +加法运算函数 x_add 从 ob_digit 数组的低位开始依次按位相加,carry做进位处理, +然后做处理a对象的高位数字,最后使用 long_normalize 函数调整 ob_size,确保ob_digit[abs(ob_size)-1]不为零,其过程大致如下图 + +![longobject x_add](longobject_x_add.png) + +减法运算函数 x_sub 的过程大致如下图 + +![longobject x_sub](longobject_x_sub.png) + +### 整数相乘 + +`源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3547) + +```c +// Objects/longobject.c +static PyObject * +long_mul(PyLongObject *a, PyLongObject *b) +{ + PyLongObject *z; + + CHECK_BINOP(a, b); + + /* fast path for single-digit multiplication */ + if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) { + stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b); + return PyLong_FromLongLong((long long)v); + } + + z = k_mul(a, b); + /* Negate if exactly one of the inputs is negative. */ + if (((Py_SIZE(a) ^ Py_SIZE(b)) < 0) && z) { + _PyLong_Negate(&z); + if (z == NULL) + return NULL; + } + return (PyObject *)z; +} +``` + +k_mul函数 [源文件]( +https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3268) + +`k_mul`函数是一种快速乘法[Karatsuba算法](https://www.wikiwand.com/zh-hans/Karatsuba算法)的实现 From 962933f103eec4089032d1c6e253d2db2bca2ca8 Mon Sep 17 00:00:00 2001 From: istommao Date: Sat, 1 Jun 2019 15:37:20 +0800 Subject: [PATCH 3/3] close #5 --- objects/long-object.md | 213 +++++++++++++++++++++++++++-------- objects/longobject_x_add.png | Bin 0 -> 5445 bytes objects/longobject_x_sub.png | Bin 0 -> 6309 bytes 3 files changed, 165 insertions(+), 48 deletions(-) create mode 100644 objects/longobject_x_add.png create mode 100644 objects/longobject_x_sub.png diff --git a/objects/long-object.md b/objects/long-object.md index 1ffed04..6722b52 100644 --- a/objects/long-object.md +++ b/objects/long-object.md @@ -109,6 +109,107 @@ PyTypeObject PyLong_Type = { }; ``` +## 创建整数对象 + +从 PyLong_Type 可以看出,创建一个整数对象的入口函数为 long_new + +`源文件:`[Objects/clinic/longobject.c.h](https://github.com/python/cpython/blob/v3.7.0/Objects/clinic/longobject.c.h#L0) + +```c +// Objects/clinic/longobject.c.h +/*[clinic input] +preserve +[clinic start generated code]*/ + +static PyObject * +long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase); + +static PyObject * +long_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"", "base", NULL}; + static _PyArg_Parser _parser = {"|OO:int", _keywords, 0}; + PyObject *x = NULL; + PyObject *obase = NULL; + + if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, + &x, &obase)) { + goto exit; + } + return_value = long_new_impl(type, x, obase); + +exit: + return return_value; +} +``` + +具体实现在 long_new_impl `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L4785) + +```c +// Objects/longobject.c + +/*[clinic input] +@classmethod +int.__new__ as long_new + x: object(c_default="NULL") = 0 + / + base as obase: object(c_default="NULL") = 10 +[clinic start generated code]*/ + +static PyObject * +long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase) +/*[clinic end generated code: output=e47cfe777ab0f24c input=81c98f418af9eb6f]*/ +{ + Py_ssize_t base; + + if (type != &PyLong_Type) + return long_subtype_new(type, x, obase); /* Wimp out */ + if (x == NULL) { + if (obase != NULL) { + PyErr_SetString(PyExc_TypeError, + "int() missing string argument"); + return NULL; + } + return PyLong_FromLong(0L); + } + if (obase == NULL) + return PyNumber_Long(x); + + base = PyNumber_AsSsize_t(obase, NULL); + if (base == -1 && PyErr_Occurred()) + return NULL; + if ((base != 0 && base < 2) || base > 36) { + PyErr_SetString(PyExc_ValueError, + "int() base must be >= 2 and <= 36, or 0"); + return NULL; + } + + if (PyUnicode_Check(x)) + return PyLong_FromUnicodeObject(x, (int)base); + else if (PyByteArray_Check(x) || PyBytes_Check(x)) { + char *string; + if (PyByteArray_Check(x)) + string = PyByteArray_AS_STRING(x); + else + string = PyBytes_AS_STRING(x); + return _PyLong_FromBytes(string, Py_SIZE(x), (int)base); + } + else { + PyErr_SetString(PyExc_TypeError, + "int() can't convert non-string with explicit base"); + return NULL; + } +} +``` + +从 long_new_impl 函数可以看出有如下几种情况 + +- x == NULL 且 obase != NULL 调用 PyLong_FromLong +- obase 为NULL 调用 PyNumber_Long +- x 和 obase 都不为 NULL + - PyUnicode 调用PyLong_FromUnicodeObject,最终调用PyLong_FromString + - PyByteArray/PyBytes 调用_PyLong_FromBytes,最终调用PyLong_FromString ## 小整数对象 @@ -293,7 +394,7 @@ print(num) ## 整数对象的数值操作 -可以看到整数对象的数值操作较多,由于篇幅限制无法一一分析,这里只分析整数的部分方法 +可以看到整数对象的数值操作较多,由于篇幅限制无法一一分析,这里只分析整数的部分操作 `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5341) @@ -340,13 +441,54 @@ static PyNumberMethods long_as_number = { ### 整数相加 -`源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L2990) +`源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3081) ```c // Objects/longobject.c -/* Add the absolute values of two integers. */ +static PyObject * +long_add(PyLongObject *a, PyLongObject *b) +{ + PyLongObject *z; + + CHECK_BINOP(a, b); + + if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) { + return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b)); + } + if (Py_SIZE(a) < 0) { + if (Py_SIZE(b) < 0) { + z = x_add(a, b); + if (z != NULL) { + /* x_add received at least one multiple-digit int, + and thus z must be a multiple-digit int. + That also means z is not an element of + small_ints, so negating it in-place is safe. */ + assert(Py_REFCNT(z) == 1); + Py_SIZE(z) = -(Py_SIZE(z)); + } + } + else + z = x_sub(b, a); + } + else { + if (Py_SIZE(b) < 0) + z = x_sub(a, b); + else + z = x_add(a, b); + } + return (PyObject *)z; +} +``` + +可以看到整数的加法运算函数long_add根据 a、b的ob_size 又细分为两个函数 (x_add 和 x_sub) 做处理 + +`源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L2991) + +```c +// Objects/longobject.c +/* Add the absolute values of two integers. */ static PyLongObject * x_add(PyLongObject *a, PyLongObject *b) { @@ -379,6 +521,17 @@ x_add(PyLongObject *a, PyLongObject *b) z->ob_digit[i] = carry; return long_normalize(z); } +``` + +加法运算函数 x_add 从 ob_digit 数组的低位开始依次按位相加,carry做进位处理,然后处理a对象的高位数字,最后使用 long_normalize 函数调整 ob_size,确保ob_digit[abs(ob_size)-1]不为零,这与普通四则运算的加法运算相同,只不过进位单元不同而已 + +![longobject x_add](longobject_x_add.png) + + +`源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3025) + +```c +// Objects/longobject.c /* Subtract the absolute values of two integers. */ @@ -423,7 +576,7 @@ x_sub(PyLongObject *a, PyLongObject *b) borrow = a->ob_digit[i] - b->ob_digit[i] - borrow; z->ob_digit[i] = borrow & PyLong_MASK; borrow >>= PyLong_SHIFT; - borrow &= 1; /* Keep only one sign bit 只保留一位符号位 */ + borrow &= 1; /* Keep only one sign bit */ } for (; i < size_a; ++i) { borrow = a->ob_digit[i] - borrow; @@ -437,50 +590,10 @@ x_sub(PyLongObject *a, PyLongObject *b) } return long_normalize(z); } - -static PyObject * -long_add(PyLongObject *a, PyLongObject *b) -{ - PyLongObject *z; - - CHECK_BINOP(a, b); - - if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) { - return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b)); - } - if (Py_SIZE(a) < 0) { - if (Py_SIZE(b) < 0) { - z = x_add(a, b); - if (z != NULL) { - /* x_add received at least one multiple-digit int, - and thus z must be a multiple-digit int. - That also means z is not an element of - small_ints, so negating it in-place is safe. */ - assert(Py_REFCNT(z) == 1); - Py_SIZE(z) = -(Py_SIZE(z)); - } - } - else - z = x_sub(b, a); - } - else { - if (Py_SIZE(b) < 0) - z = x_sub(a, b); - else - z = x_add(a, b); - } - return (PyObject *)z; -} ``` -可以看到整数的加法运算函数long_add根据 a、b的ob_size 又细分为两个函数做处理 x_add 和 x_sub - -加法运算函数 x_add 从 ob_digit 数组的低位开始依次按位相加,carry做进位处理, -然后做处理a对象的高位数字,最后使用 long_normalize 函数调整 ob_size,确保ob_digit[abs(ob_size)-1]不为零,其过程大致如下图 - -![longobject x_add](longobject_x_add.png) - -减法运算函数 x_sub 的过程大致如下图 +与普通四则运算减法相同,数不够大则向高一位借位, +减法运算函数 x_sub 的示例图如下,注:PyLong_SHIFT为30 ![longobject x_sub](longobject_x_sub.png) @@ -514,7 +627,11 @@ long_mul(PyLongObject *a, PyLongObject *b) } ``` -k_mul函数 [源文件]( +k_mul函数是一种快速乘法 [源文件]( https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3268) -`k_mul`函数是一种快速乘法[Karatsuba算法](https://www.wikiwand.com/zh-hans/Karatsuba算法)的实现 +> Karatsuba的算法主要是用于两个大数的乘法,极大提高了运算效率,相较于普通乘法降低了复杂度,并在其中运用了递归的思想。 +> 基本的原理和做法是将位数很多的两个大数x和y分成位数较少的数,每个数都是原来x和y位数的一半。 +> 这样处理之后,简化为做三次乘法,并附带少量的加法操作和移位操作。 + +具体可以看wiki [Karatsuba算法](https://www.wikiwand.com/zh-hans/Karatsuba算法)的实现 diff --git a/objects/longobject_x_add.png b/objects/longobject_x_add.png new file mode 100644 index 0000000000000000000000000000000000000000..63cb695fa810b2a8df5f44302a12c26dbbace5f9 GIT binary patch literal 5445 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 literal 0 HcmV?d00001 diff --git a/objects/longobject_x_sub.png b/objects/longobject_x_sub.png new file mode 100644 index 0000000000000000000000000000000000000000..e500b1257e068ac1c95183747e23715fa535ba94 GIT binary patch literal 6309 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 US{6!k`{dOIx<)#cTE9N|FHNB%DF6Tf literal 0 HcmV?d00001