《Python 源码剖析》 之 int

int对象,显然是不可变的对象。那么对于数字,引用的开销显然大,new和free比较费性能。所以py是维护了一个一定范围的Int值,类似于一个int对象池子。和java的string类似,但是java是有普通类型int。
其实, 小整数对象池就是一个PyIntObject指针数组(注意是指针数组), 大小=257+5=262, 范围是[-5, 257) 注意左闭右开. 即这个数组包含了262个指向PyIntObject的指针.

>>> a = 1
>>> b = 1
>>> id(a) == id(b)
True

>>> c = 257  超过256之后
>>> d = 257
>>> id(c) == id(d)
False

PyIntObject

typedef struct {
    PyObject_HEAD   //int是固定长度对象 PyObject(ob_refcnt引用计数 *ob_type 类型)
    long ob_ival; //
} PyIntObject;
几个构造方法
# 从字符串, 生成PyIntObject对象
PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int);

# 从Py_UNICODE, 生成PyIntObject对象
#ifdef Py_USING_UNICODE
PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
#endif

# 从long值, 生成PyIntObject对象 **最后都调用这个方法完成对象生成。即int就是long**
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);

PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t);
PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t);
具体的最终构造方法 PyInt_FromLong,别的都是str还是Unicode最终都是调用PyInt_FromLong。
PyObject *
PyInt_FromLong(long ival)
{
    register PyIntObject *v;

    /* MARK: 如果, 先判断数值是否是小整数,值在小整数范围内, 是的话直接从小整数对象池获取得到对象 */
    #if NSMALLNEGINTS + NSMALLPOSINTS > 0
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {

        /* MARK: small_ints是什么后面说 */
        v = small_ints[ival + NSMALLNEGINTS];
        // 引用计数+1
        Py_INCREF(v);

        /* 这里先忽略, 计数 */
        #ifdef COUNT_ALLOCS
            if (ival >= 0)
                quick_int_allocs++;
            else
                quick_neg_int_allocs++;
        #endif

        // 返回
        return (PyObject *) v;
    }
    #endif

    // 如果不是小整数, 从通用整数对象池里面取一个, 初始化返回。
    //如果free_list还不存在, 或者满了
    if (free_list == NULL) {
        // 新建一块PyIntBlock, 并将空闲空间链表头部地址给free_list
        if ((free_list = fill_free_list()) == NULL)
            // 如果失败, 返回
            return NULL;
    }

    // 从free_list分出一个位置存放新的整数

    /* Inline PyObject_New */
    // 使用单向链表头位置
    v = free_list;

    // free_list指向单向链表下一个位置
    free_list = (PyIntObject *)Py_TYPE(v);

    // 初始化对象, 类型为PyInt_type, 值为ival
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;

    // 返回
    return (PyObject *) v;
}
  • 先判断数值是否是小整数, 是的话从小整数对象池里面直接返回(这个池固定大小, 下一点讲)
  • 如果不是, 从通用整数对象池里面取一个, 初始化返回
    (如果这时候通用整数对象池还不存在或者已经满了, 新建一个池加入维护. 通用整数对象池后面讲)
小整数对象池

其实, 小整数对象池就是一个PyIntObject指针数组(注意是指针数组), 大小=257+5=262, 范围是[-5, 257) 注意左闭右开. 即这个数组包含了262个指向PyIntObject的指针.
创建整数对象时,先检查是不是小整数, 如果在[-5, 257)范围, 直接返回已经存在的整数对象指针, 所以我们看到开头的例子, id比较一个true/一个false

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif

#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array
   so that they can be shared.
   The integers that are saved are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/

static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif
小整数对象池, 在一开始就初始化了, 其初始化代码
int
_PyInt_Init(void)
{
    PyIntObject *v;
    int ival;

    // 注意这里, free_list再次出现

#if NSMALLNEGINTS + NSMALLPOSINTS > 0

    // 循环, 逐一生成
    for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
          if (!free_list && (free_list = fill_free_list()) == NULL)
                  return 0;

        // 注意这段代码, 和上面PyInt_FromLong那段代码一样的
        /* PyObject_New is inlined */
        v = free_list;
        free_list = (PyIntObject *)Py_TYPE(v);
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;

        // 放到数组里
        small_ints[ival + NSMALLNEGINTS] = v;
    }
#endif

    return 1;
}

结论

  1. 小整数对象池缓存 [-5, 257) 内的整数对象, 数值在这个范围的整数对象有且只存在一个… 是提前初始化的唯一不重复的。
  2. 小整数对象池, 只是一个指针数组, 其真正对象依赖通用整数对象池
  3. 小整数:在[ -NSMALLNEGINTS, NSMALLPOSINTS )范围内的整数定义为小整数,缓存在Int对象池中,在Python运行过程中不会被销毁,用于快速引用(比如用于循环变量时)。其值范围可以手工定制你需要的范围,但是得重新编译python代码。
  4. 普通整数:除了小整数范围内的整数都为普通整数,存储在通过由block_list链表链接起来的对象池中。
struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};

typedef struct _intblock PyIntBlock;
static PyIntObject *free_list = NULL;
由free_list空闲链表将未使用的objects内存链接起来。注意,在free_list中,由ob_type域充当指向下一节点的指针,显然这里放弃了类型安全。并且链表表头在数组尾部。
每次需要申请一个整数对象,从free_list表头摘取一个对象,再将free_list指向下一个元素。
当一个对象的内存被回收时,重新将该对象链入free_list。
小整数由于首先被初始化,所以位于block_list链表尾端

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值