日期 分类 Python

Python在处理整数对象时,根据使用频率,将整数对象分为小整数对象和大整数对象,分别用不同的内存管理策略来处理。以下简单介绍两种方式的具体实现和运行机制是怎样的。

小整数对象

实际编程中,一些范围较小的整数会频繁使用,比如1,2,9等。如果每次对这些小整数的使用都要申请内存和释放内存,无疑会非常影响运行效率,并可能在堆上产生内存碎片。

所以Python对这类较小的整数使用了对象池技术,具体则是通过在Python中直接缓存这类对象到内存中来实现的。这类对象具体范围和具体由哪个变量来维护在源码中都有定义

[intobject.c]
#ifndef NSMALLPOSINTS
    #define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
    #define NSMALLNEGINTS 5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]
#endif

由源码可以看出小整数的默认范围是[-5, 257),这个范围可以根据自己的使用情况做修改。

大整数对象

Python不可能把所有范围的整数都直接缓存到内存中,所以超过小整数对象范围的数字(即大整数对象),Python使用了另外的内存管理策略。

第一次创建一个大整数对象,Python会在申请一个较大的内存块,这个内存块结构是用PyIntBlock来表示的

[intobject.c]
struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

这个PyIntBlock内包含一个PyIntObject数组objects,后续新的大整数对象创建,会复用objects的内存空间。直到这个内存块的空间不够使用,Python再创建的PyIntBlock对象,新的PyIntBlock对象通过next指针指向旧的PyIntBlock对象,而block_list指向新的PyIntBlock对象,所以整个PyIntBlock链是由block_list维护的。

另外,所有空闲可使用PyIntObject内存是由free_list维护的。实际上这些空闲内存组成了一个单项链表,当PyIntBlock对象被创建时,里面objects数组会被按数组顺序逆序通过PyInBlock中的ob_type连接组成一个单项链表,free_list指向这个链表表头。当某个被使用的PyIntObject对象被销毁时,其实也并不会释放内存,只是被加入到这个free_list中。


参考资料