这篇文档主要介绍CPython中的整数对象
在这之前先贴一些Python代码
>>> id(1)
140574362462680
>>> id(2)
140574362462656
>>> id(3)
140574362462632
>>> id(4)
140574362462608
内存地址竟然是连续的
id(1) - 24 = id(2)
id(2) - 24 = id(3)
id(3) - 24 = id(4)
天啦噜
一句话介绍:python整数对象的值是一个c语言的long类型
我也是看到哪写到哪里,请随意看看
上来先贴一个int内存池的初始化方法
简单看一下,看不懂也没有关系,重点看字
static PyIntObject *
fill_free_list(void)
{
PyIntObject *p, *q;
/* Python's object allocator isn't appropriate for large blocks. */
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
if (p == NULL)
return (PyIntObject *) PyErr_NoMemory();
((PyIntBlock *)p)->next = block_list;
block_list = (PyIntBlock *)p;
/* Link the int objects together, from rear to front, then return
the address of the last int object in the block. */
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
while (--q > p)
q->ob_type = (struct _typeobject *)(q-1);
q->ob_type = NULL;
return p + N_INTOBJECTS - 1;
}
可以看到PyIntObject指针p指向了刚分配的PyIntBlock的起始地址
这里把PyIntObject对象指针强制转为了PyIntBlock指针
这里的强制转换是可以的,因为分配的内存大小本来就是PyIntBlock
那么什么是PyIntBlock
来看一下PyIntBlock的结构
#define BLOCK_SIZE 1000 /* 1K less typical malloc overhead */
#define BHEAD_SIZE 8 /* Enough for a 64-bit pointer */
#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))
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的数组object
看起来PyIntBlock是一个PyIntObject的集合
还有一个next指针
next指针说明这个结构体主要作为单链表使用
#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))
sizeof(PyIntObject)在我的电脑上为24 (Macbook Pro mid 2015)
可通过以下python代码查看
import sys
sys.getsizeof(1)
>>> 24
所以objects的大小为41
也是就是说每个PyIntBlock包含41个PyIntObject
我们还看到了
block_list
free_list
这两个是什么呢,只能接着往下看
来扫一眼这堆宏
#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
#ifdef COUNT_ALLOCS
int quick_int_allocs, quick_neg_int_allocs;
#endif
定义了NSMALLPOSINTS(n small positive ints) 257
NSMALLNEGINTS(n small negative ints) 5
然后定义了一个 small_ints指针,指向大小为(257+5)的PyIntObject数组
small_ints 为指针数组(数组中存放的都是指针)
现在是创建PyIntObject的函数
PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
#ifdef COUNT_ALLOCS
if (ival >= 0)
quick_int_allocs++;
else
quick_neg_int_allocs++;
#endif
return (PyObject *) v;
}
#endif
if (free_list == NULL) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)v->ob_type;
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
这个函数先判断一下这个long类型变量ival是否在[-5,257)范围内
如果在的话直接从small_ints这个指针数组中取出一个指向PyIntObject的指针,并且将该指针指向对象的引用计数加1
如果不在的话判断一下free_list是否为空,为空的话
执行 free_list = fill_free_list()
也就是到了我们最开始的那个函数
这个free_list呢,是一个整数空闲空间对象池
那么现在的问题是什么?,我们先把代码贴下来,方便讨论
static PyIntObject *
fill_free_list(void)
{
PyIntObject *p, *q;
/* Python's object allocator isn't appropriate for large blocks. */
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
if (p == NULL)
return (PyIntObject *) PyErr_NoMemory();
((PyIntBlock *)p)->next = block_list;
block_list = (PyIntBlock *)p;
/* Link the int objects together, from rear to front, then return
the address of the last int object in the block. */
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
while (--q > p)
q->ob_type = (struct _typeobject *)(q-1);
q->ob_type = NULL;
return p + N_INTOBJECTS - 1;
}
p开始是一个PyIntObject指针
然后申请了一个sizeof(PyIntBlock)空间
p指向新申请空间的起始地址
然后p被转换为了一个PyIntBlock指针
然后p->next指向了block_list的起始地址
然后block_list指向了p
也就是说现在的block_list已经有一个PyIntBlock的内存空间了
接下来做了些什么?
接下来p又指向了刚分配内存空间的PyIntBlock结构体objects数组的第一个PyIntObject起始地址
q则指向了objects数组最后一个元素的起始地址
接下来据说是把objects中的PyIntObject对象依次用ob_type指针链接起来
但是现在问题是ob_type是个什么玩意?
这个时候来回顾一下PyIntObject
typedef struct _object {
PyObject_HEAD
} PyObject;
#define PyObject_HEAD \
_PyObject_HEAD_EXTRA \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
现在可以看到ob_type是指向_typeobject结构体的指针
所以我们还得看一下——typeobject是什么,我把代码贴过来
天啦噜 代码太长了不贴了,反正已经知道是指针了 天下指针都一样
下面着重分一下这两行代码干了什么
while (--q > p)
q->ob_type = (struct _typeobject *)(q-1);
指针q指向一个PyIntObject对象,
现在让q指向的对象的ob_type指针指向q前面一个元素
这样下来所有的PyIntBlock的objects内的所有PyIntObject通过ob_type链接起来成为了一个单向链表
q->ob_type = NULL; //处理第一元素
最后返回了最后一个PyIntObject元素的地址
这个地址也就是free_list的起始地址
这样的话free_list就有了空间
其实就是free_list分配空间的过程
接下来回到
PyInt_FromLong这个函数
为了看起来方便,我把代码贴下来
PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
#ifdef COUNT_ALLOCS
if (ival >= 0)
quick_int_allocs++;
else
quick_neg_int_allocs++;
#endif
return (PyObject *) v;
}
#endif
if (free_list == NULL) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)v->ob_type;
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
现在
v = free_list;
这里v就是指向刚分配的PyIntBlock的objects数组的最后一个元素
然后free_list沿着链表指向了objects中的前一个元素
现在调用了PyObject_INIT()函数,先不管这个函数,就当它是初始化好了
然后给v指向的PyIntObject的ob_ival赋值
最后返回一个PyObject对象的指针
注意哦 这里不是PyIntObject
从函数定义上也能看出来
PyObject * PyInt_FromLong(long ival)
至于是为什么,我知道了再更新在下面
现在呢当你调用了PyInt_FromLong方法之后
就会得到一个指向个这个整数PyIntObject的指针
现在我们就知道当生成一个大整数对象的时候要从free_list中拿到分配的空间
只有当free_list == NULL的时候才会重新申请一个PyIntBlock
之前提到了block_list
什么是block_list?
((PyIntBlock *)p)->next = block_list;
block_list = (PyIntBlock *)p;
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;
free_list是一个PyIntObject链表
block_list是一个PyIntBlock链表
每次分配的PyIntBlock都会加入到block_list
free_list则指向PyIntBlock objects中的PyIntObject
block_list指向的永远是最近创建的PyIntBlock
这个时候出现重大问题
block_list 这个链表中一旦有其中一个PyIntBlock中的objects中的PyIntObject需要被释放,那怎么办?
答案是python在回收的时候把空闲的全都链接到了一起
那么链接到什么地方去了
空闲空间怎么复用
原有objects链表如何链接?
只能继续往下看
PyIntObject的销毁
static void
int_dealloc(PyIntObject *v)
{
if (PyInt_CheckExact(v)) {
v->ob_type = (struct _typeobject *)free_list;
free_list = v;
}
else
v->ob_type->tp_free((PyObject *)v);
}
大致扫一眼就知道了
回收的PyIntObject对象空间被放到了free_list的链表的头部
v->ob_type = (struct _typeobject *)free_list;
free_list = v;
也就是说这部分内存并没有被释放
这里请注意,永远没有被释放
一个PyIntObject 24个字节
1GB内存 理论上可以容纳 44739242 个整数对象,实际更少
那么 大整数就是这样了 做个简单总结
当要生成一个大整数对象的时候
先去free_list中取空间
没有就初始化free_list
每一次初始化就会申请一个PyIntBlock的空间
空间大小为1000-8 = 992个字节(992B)
这些空间为41个整数
所有的PyIntBlock都在block_list这个链表上
而free_list则是所有这些PyIntBlock中可用PyIntObject的链表
当空间需要回收的时候,就把这个空间放到free_list链表的头部,以备下次使用
现在关注一下小整数对象池small_ints
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
int
_PyInt_Init(void)
{
PyIntObject *v;
int ival;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
if (!free_list && (free_list = fill_free_list()) == NULL)
return 0;
/* PyObject_New is inlined */
v = free_list;
free_list = (PyIntObject *)v->ob_type;
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
small_ints[ival + NSMALLNEGINTS] = v;
}
#endif
return 1;
}
这段代码还是很清晰的,
从free_list中取空间,然后挨个初始化[-5,257)中所有的整数对象
然后加入small_ints这个指针数组中,下标就是对应的整数值
这里值得注意的是申请空间同样是通过fill_free_list实现
也就是说小整数对象也是在block_list中的
最后总结
当使用python整数的时候,申请空间不是24B一个一个申请的,而是一次申请992B
也就是说一次申请41个PyIntObject的空间
而小整数[-5, 257) 永远占据着内存空间,并且在Python启动的时候就完成初始化了
并且之后申请的整数空间Python也不会释放,而是放到整数空间池free_list里
就这样直到地老天荒~
想要试一下内存占用是否真的那么夸张?
来执行下下面代码
for i in range(44739242):
pass
在系统监视器或者任务管理器里面查看一下内存占用
~= 1GB
EOF
0 许可 CC BY-SA 3.0参考文档
[1]. 《Python源码剖析》
© 2017 Leo Howell