# int

# 内部结构

# _longobject PyLongObject

struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1]; 
            // 对于比较大的整数, Python 将其拆成若干部分,保存在 ob_digit 数组中。
			//C 语言中数组长度不是类型信息,我们可以根据实际需要为 ob_digit 数组分配足够的内存,并将其当成长度为 n 的数组操作。
            // 数组的长度 由PyObject_VAR_HEAD宏定义中的ob_size存储
};

typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */

# digit

#if PYLONG_BITS_IN_DIGIT == 30
typedef uint32_t digit; 	// 32位
// ...
#elif PYLONG_BITS_IN_DIGIT == 15
typedef unsigned short digit;	// 16位
// ...
#endif

image

# PyLong_Type

PyTypeObject PyLong_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "int",                                      /* tp_name */
    offsetof(PyLongObject, ob_digit),           /* tp_basicsize */
    sizeof(digit),                              /* tp_itemsize */
    long_dealloc,                               /* tp_dealloc */

    //...

    &long_as_number,                            /* tp_as_number */

    //...

    long_new,                                   /* tp_new */
    PyObject_Del,                               /* tp_free */
};

# long_as_number

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*/
    (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*/
    // ...
};

image

# 整数布局

  • 整数 绝对值 拆分成多个部分,存放于 底层整数数组 ob_digit ;
  • 底层数组长度保存在 ob_size 字段,如果整数为负, ob_size 也为负;
  • 对于整数 0 ,底层数组为空, ob_size 字段为 0 ;

例如:

  1. 对于整数 0 , ob_size 字段等于 0 , ob_digit 数组为空,无需分配。

  2. 对于整数 10 ,其绝对值保存于 ob_digit 数组中,数组长度为 1 , ob_size 字段等于 1 。

  3. 对于整数 -10 ,其绝对值同样保存于 ob_digit 数组中,但由于 -10 为负数, ob_size 字段等于 -1 。

  4. 对于整数 1073741824 ( 2 的 30 次方),由于 ** Python 只使用 32 整数的后 30 位**,需要另一个整数才能存储,整数数组长度为 2 。绝对值这样计算:2^30*1+2^0*0=1073741824​。

    如果全部 32 位都用来保存绝对值,那么为了保证加法不溢出 (产生进位),需要先强制转换成 64 位类型后在进行计算。但牺牲最高 1 位后,加法运算便不用担心进位溢出了。

    那么,为什么 Python 牺牲最高 2 位呢?我猜这是为了和 16 位整数方案统一起来:如果选用 16 位整数作为数组, Python 则只使用其中 15 位。

  5. 对于整数 -4294967297 (负的 2 的 32 次方加 1 ),同样要长度为 2 的 ob_digit 数组,但 ob_size 字段为负。绝对值这样计算:2^30*4+2^0*1=4294967297​。

# 内存优化

# 小整数静态对象池

为什么是对象池而不是空闲对象链表:

缓存池比空闲链表更快,因为对象是已经初始化好了的,直接拿过来就能用。空闲链表只是解决了内存频繁分配的问题,省去了申请内存的开销,但仍然需要重新初始化对象。另外,由于int对象是不定长的,实现空内存链表的话,也只能保存固定大小的一类,比如只缓存底层数组只有一个元素的对象,或者安装底层数组长度,分类缓存。

同float一样,为了避免运行时大量整数对象被创建销毁(例如for i in range(100)​),严重拖慢性能,Python提供了一个小整数池的解决方案:提前将常用的整数对象创建好,以备后用。

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257 // 正数个数,默认从0开始
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5  // 负数个数
#endif

static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; // 小整数池

image

# 常用整数优化

注意在3.11中如下的解释器层级的优化是存在的,但是在更低版本的python中可能不成立。

虽然python仅对[-5,256]​的小整数建立常量池,但是对于超过这个范围的常用整数也做了一定优化:对于在编译时就可以确定值相同的变量让指向同一个对象。

例如下面例子中的1235,都是在执行前就可以确定,因此所有数实际都指向到同一个对象,但是例子4由于要进行运算才能知道值因此没有指向同一个对象。

a = 10000
b = 10000
print(a is b) # True

a = 10000
b = 10000 + 0
print(a is b) # True

a = 10000
b = a
print(a is b) # True

a = 10000
b = a + 0
print(a is b) # False

a = 10 ** 3
b = 10 * 100
c = 9 * 111 + 1
d = 1000
print(a is b is c is d) # True

# 奇巧淫技

可以dis一下整数的+=,可以发现其实际上分为了两个字节码,即a+=b​实际上和a=a+b​是一致的,都是先计算结果然后赋值给a,既然有了两个字节码,就有可能出现racing的情况,但是我们可以用一种奇特的方法实现整数不会racing的+1​操作:

>>> dis.dis("n=1;n+=1;")
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (n)
              4 LOAD_NAME                0 (n)
              6 LOAD_CONST               0 (1)
              8 INPLACE_ADD
             10 STORE_NAME               0 (n)
             12 LOAD_CONST               1 (None)
             14 RETURN_VALUE
# 这段代码来自asyncio
# 生成新的 task 时的命名计数器
# 这里不采用 +=1 的操作是因为协程并非线程安全
# 通过迭代器不断的向后计数,可以完美的保证线程安全
_task_name_counter = itertools.count(1).__next__

# 使用方式
if name is None:
    self._name = f'Task-{_task_name_counter()}'