Python 整数对象的实现
Python 的内建对象存放在源代码的Objects
目录下。
intobject.c
用于整数对象
在 Python 中,整数分为小整数对象
和大整数对象
小整数对象
由于数值较小的整数对象在内存中会很频繁地使用,如果每次都向内存申请空间、请求释放,会严重影响 Python 的性能。好在 整数对象 属于不可变对象,可以被共享而不会被修改导致问题,所以为 小整数对象 划定一个范围,即小整数对象池,在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];
从源代码可以看出
define NSMALLPOSINTS 257
,范围的右边界
define NSMALLNEGINTS 5
,范围的左边界
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive)
,[-5, 257)
大整数对象
但是,整数对象很多,不一定都是小整数对象,又不能将所有整数对象都放入内存。于是,Python 提供了一个可扩展的内存空间,称为通用整数对象池
,谁需要用就给谁用,这样免去了申请空间,又能提高一些效率。
这个空间是一个PyIntBlock结构,是用一个单向列表连接一串内存(block),这个列表由block_list
维护,而每个 block 维护一个 整数对象数组(Objects),用于存放被缓存的整数对象。block_list
的内容是最新创建的 block。
小整数对象池 也在block_list
上。
Python 使用一个单向链表管理全部 block 的 objects 中的所有空闲内存,由free_list
指出下一个可用的空闲内存。如果当前没有空闲内存,free_list
为NULL
,会创建新的内存。
当整数对象的引用计数变为0,会销毁对象,但并不会释放空闲出来的内存,即将内存交还系统,而是重新加入free_list
。
hack
使用Xcode
修改打印整数对象的方法
原始文件
/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)
/* flags -- not used but required by interface */
{
long int_val = v->ob_ival;
Py_BEGIN_ALLOW_THREADS
fprintf(fp, "%ld", int_val);
Py_END_ALLOW_THREADS
return 0;
}
修改后,可以打印部分小整数地址池中,整数对象的引用次数、所在内存地址、下一个可用的空闲内存地址
/* ARGSUSED */
static int values[10];
static int refcounts[10];
static int
int_print(PyIntObject *v, FILE *fp, int flags)
/* flags -- not used but required by interface */
{
PyIntObject* intObjectPtr;
PyIntBlock *p = block_list;
PyIntBlock *last = NULL;
int count = 0;
int i;
while(p != NULL)
{
++count;
last = p;
p = p->next;
}
intObjectPtr = last->objects;
intObjectPtr += N_INTOBJECTS - 1;
printf(" address @%p\n", v);
for(i = 0; i < 10; ++i, -- intObjectPtr)
{
values[i] = intObjectPtr -> ob_ival;
refcounts[i] = intObjectPtr -> ob_refcnt;
}
printf(" value : ");
for(i = 0; i < 8; ++i)
{
printf("%d\t", values[i]);
}
printf("\n");
printf(" refcnt : ");
for(i = 0; i < 8; ++i)
{
printf("%d\t", refcounts[i]);
}
printf("\n");
printf(" block_list count : %d\n", count);
printf(" free_list : %p\n", free_list);
return 0;
/* long int_val = v->ob_ival;
Py_BEGIN_ALLOW_THREADS
fprintf(fp, "%ld", int_val);
Py_END_ALLOW_THREADS
return 0;*/
}
保存并编译、安装,运行修改后的 Python
Python 2.6.9 (unknown, Nov 1 2015, 20:22:05)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> i = -9999
>>> i
address @0x7fe412f16470 # -9999 所在的内存地址
value : -5 -4 -3 -2 -1 0 1 2 # 能够显示的 -5 ~ 2
refcnt : 1 1 1 1 35 105 64 41 # 引用计数器,可以看到,小整数已经被Python自身使用了多次
block_list count : 8 # block 数量
free_list : 0x7fe412f16488 # 下一个可用的空闲内存地址
>>>
>>> a = -258
>>> a
address @0x7fe412f16488 # -258 的内存地址,是上面`free_list`指出的空闲内存
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 1 1 1 1 35 105 64 41
block_list count : 8
free_list : 0x7fe412f164a0 # 新的空闲内存地址
>>> b = -258
>>> b
address @0x7fe412f164a0 # 上一个的空闲内存地址,可以看出,对于多次创建的大整数对象,即使值一样,也是不同的内存地址
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 1 1 1 1 35 105 64 41
block_list count : 8
free_list : 0x7fe412f164b8
>>> del b # 释放 b 的内存空间
>>> a
address @0x7fe412f16488
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 1 1 1 1 35 105 64 41
block_list count : 8
free_list : 0x7fe412f164a0 # 删除 b 后,新的空闲内存重新加入`free_list`,没有归还给系统
>>> c1 = -5 # 属于小整数对象池
>>> c1
address @0x7fe412f033d8
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 5 1 1 1 35 105 64 41 # -5 引用此时为 5
block_list count : 8
free_list : 0x7fe412f164b8
>>> c2 = -5 # 同上
>>> c2
address @0x7fe412f033d8 # 两次创建的相同小整数对象,指向了相同的内存地址
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 6 1 1 1 35 105 64 41 # -5 的引用次数加一,变为 6
block_list count : 8
free_list : 0x7fe412f164b8
>>>
整数对象的说明文件内置在源代码中:
PyDoc_STRVAR(int_doc,
"int(x[, base]) -> integer\n\
\n\
Convert a string or number to an integer, if possible. A floating point\n\
argument will be truncated towards zero (this does not include a string\n\
representation of a floating point number!) When converting a string, use\n\
the optional base. It is an error to supply a base when converting a\n\
non-string. If base is zero, the proper base is guessed based on the\n\
string content. If the argument is outside the integer range a\n\
long object will be returned instead.");
int_doc
就是整数对象的__doc__
属性
参考资料
《Python 源码剖析》第二章:整数对象