Int 可以说是Python里最简单的对象了,我们也应该可以想象的到,它应该是把C里面的int或者long包装了一下,那么,仅仅是包装一下而已吗?下面是PyIntObject 的定义

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;



确实非常简单,可以看到,PyIntObject的头部是PyObject_Head,由此可以看出int确实是一个定长对象(定长和不定长在上一篇中解释过),然后就是封装了一个C语言里面的long类型的一个数值。在这里要强调一点,Python里面数值类型分为Int ,Long, Int和C里面的一样,Long却是比C里面的Long要大的多,和Java里面的BigInteger差不多。

这里我们主要关注的是Python Int 的对象池的实现。可以想象,我们平时使用int是多么的频繁,随便一个循环就会遍历好多int对象,那如果都是用到了再malloc,用完了就free的话,我想这效率应该任何人都忍受不了吧,所以才有了整数类型的对象池这一玩意~

Python里面的整数分为小整数对象和大整数对象。想想也对,我们经常会写

for(int i=0;i<XXX;i++){}

这样的代码,那么0,1,2,3这种比较小的整数我们会使用的更加频繁,要是他们能一直留在内存里,就不用一直malloc和free了,岂不是好事?那怎样算是大整数对象呢?100算大么?10000算大么?很难定义,python里面的划分界限个人猜测应该是根据大量的实验才定的吧。

#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];


这是小整数对象池的定义(在intObject.c里面),也就是说,Python里面默认的是[-5,257)算是小整数(Python2.7版本是这样,Python3.3版本里面就没有intObject的定义,int和Long直接结合在一起,上面的代码也直接定义在了longObject.c里面),在init的时候,Python直接把这些数存在内存里面,将他们的地址存在small_ints这个数组里面,一旦要用的时候就直接来这个数组拿,完全省去了malloc和free的开销。这个small_ints自init之后就与天地同寿了:)我们也可以自己修改源代码来自己定义小整数对象,然后自己build self-defined python......

那对于大整数对象有什么优化呢?Python 里面有这么一个结构:

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;


static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;



Python运行环境会给大整数对象分配一定的内存空间,哪个对象要用了,那就直接使用空着的空间。

PyIntObject 是作为数组存在PyIntBlock里面的,一个block大约是存82个PyIntObject对象(也可以自己修改再重新build)。block_list是用来维护所有给整数对象分配的内存空间,而里面的哪些空间是空的可用的,则由free_list在维护。当free_list为NULL的时候,也就是没有空余空间的时候,Python会运行fill_free_list()这个函数在malloc一个block出来. 要注意的是,小整数对象也是生存在由block_list维护的内存上面的。至于怎么在插入删除元素的时候维护free_list,和我们平时写的链表操作差不多,这里不详谈。

总而言之,当数字较小的时候,就直接使用一直存在的,早就init好的小整数对象池,当数字较大的时候,就用通用的整数对象池。