目录
1.初识整数
2.PyIntObject创建与维护
2.1 对象的创建途径
2.2小整数对象
2.3大整数对象
2.4 添加和删除
python中整数是通过PyIntObject对象实现的,它属于imutable(不可变对象),也就是说在创建一个PyIntObject对象后,就再也不能改变该对象的值了;另外这里的int并非真正的int,而是long 类型,我们看下它的结构源码可以知道:
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
在python程序中,整数使用是非常广泛的,频繁的创建和释放内存是非常影响执行效率的。假如直接使用引用计数机制,执行下面这个循环的话。
>>> for i in range(10000)
这个for循环需要10000次申请和释放内存,这意味着堆需要面临整数对象狂风暴雨的访问,这样的执行效率肯定是无法接受的,那么设计一个高效的机制避免整数对象的使用成为python的瓶颈是至关重要的。Guido给出的解决方案是整数对象池:小整数对象池和通用整数对象池。整数对象池是本章的重点。
在讲解整数对象池之前我们先看看PyIntObject对象的元信息:
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type)
"int",
sizeof(PyIntObject),
(destructor)int_dealloc, /* tp_dealloc */
(printfunc)int_print, /* tp_print */
(cmpfunc)int_compare, /* tp_compare */
(reprfunc)int_repr, /* tp_repr */
&int_as_number, /* tp_as_number */
(hashfunc)int_hash, /* tp_hash */
(reprfunc)int_repr, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE, /* tp_flags */
int_doc, /* tp_doc */
int_methods, /* tp_methods */
int_new, /* tp_new */
(freefunc)int_free, /* tp_free */
......
};
从上面可以知道元信息包含了PyIntObject的应该占用的内存大小,对象文档信息,还有PyIntObject支持的操作,部分操作功能如下表:
int_dealloc | 析构操作 |
int_print | 打印操作 |
int_compare | 比较操作 |
int_hash | 获得hash值 |
int_as_number | 数值操作集合 |
int_repr | 转化为PyStringObject对象 |
int_free | 释放操作 |
int_methods | 成员函数集合 |
我先分析下python中compare函数的实现,源码如下:
static int
int_compare(PyIntObject *v, PyIntObject *w)
{
register long i = v->ob_ival;
register long j = w->ob_ival;
return (i < j) ? -1 : (i > j) ? 1 : 0;
}
从源码可以看出,对比操作实际上就是简单的将它所维护的long值进行对比。这里还要介绍下int_methods 这个域,它包含了几十种数值操作,如下:
static PyNumberMethods int_as_number = {
(binaryfunc)int_add, /*nb_add*/
(binaryfunc)int_sub, /*nb_subtract*/
(binaryfunc)int_mul, /*nb_multiply*/
(binaryfunc)int_classic_div, /*nb_divide*/
(binaryfunc)int_mod, /*nb_remainder*/
.....
};
这里给大家讲解其中的add操作,我们先看看它的源码:
#define PyInt_AS_LONG(op) (((PyIntObject *)(op))->ob_ival)
#define CONVERT_TO_LONG(obj, lng) \
if (PyInt_Check(obj)) { \
lng = PyInt_AS_LONG(obj); \
} \
else { \
Py_INCREF(Py_NotImplemented); \
return Py_NotImplemented; \
}
static PyObject *int_add(PyIntObject *v, PyIntObject *w)
{
register long a, b, x;
CONVERT_TO_LONG(v, a);
CONVERT_TO_LONG(w, b);
x = a + b;
if ((x^a) >= 0 || (x^b) >= 0)
return PyInt_FromLong(x);
return PyLong_Type.tp_as_number->nb_add((PyObject *)v, (PyObject *)w);
}
宏PyInt_AS_LONG的意义是可以省去一次函数调用的开销,但是牺牲了类型安全。我们可以从源码看出,整数对象确实是imutable,两数相加操作完成后,参与的对象都没有发生改变,取而代之的是一个全新的PyIntObject。如果相加后发生来溢出,则返回PyLongObject对象。
源码里提供了5种创建PyIntObject对象的办法:
PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int);
#ifdef Py_USING_UNICODE
PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
#endif
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t);
PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t);
分别从字符串,long,unicode,Py_ssize_t,size这五种类型进行转换,其中前三种是最常见的。为了深刻理解PyIntObject的创建过程,我们必须了解python中整数对象在内存的组织方式。
在编程中,有些小数值使用非常频繁,不可能每次都要重新创建,这样太浪费时间了,Guido使用了一个对象池,将所有小数值对象都丢在池里面,池里的任意对象都可以被共享。那有一个问题,多小的数值属于小整数,分界点在哪里??
python提供了一种方法来修改边界,但是需要修改源码,大小整数边界定义源码如下:
#define NSMALLPOSINTS 257
#define NSMALLNEGINTS 5
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* 对小整数的引用保存在这个数组中,以便它们可以共享。
保存的整数是从NSMALLNEGINTS(包含)到NSMALLPOSINTS(不包含)的整数。
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
这里将小整数集合范围设置为[-5,257),我们可以通过NSMALLNEGINTS和NSMALLPOSINTS修改小整数边界。然后根据边界将小整数缓存到内存中直到python程序结束为止。其中指针small_ints指向小整数内存块。
谁敢保证只有小整数才会被频繁使用,但是把所有数值对象都缓存到内存的话又会被别人笑话。于是乎python设计者们作出的妥协,对于小整数,完全地缓存在小整数对象池,对于其他整数来说,python运行环境将提供一块内存空间,这些内存空间由这些大整数轮流使用,也就是说,谁需要的时候谁就使用。下面将详细剖析其实现机制。
在python中,有一个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内部维护一块(BLOCK_SIZE - BHEAD_SIZE)大小的内存(block)空间,PyIntBlock的单向列表通过block_list来维护,每个block都拥有一个objects,objects用于存储被缓存的对象的内存。如图:
我们可以想象,block中的objects有些被使用了,而有些还处于空闲状态,这些空闲内存必须组织起来,这样,python在需要新内存来存储新对象时才能快速获取所需内存,python使用来一个单向链free_list来维护,free_list就是自由内存链表的表头,如果还有空闲空间,新增的对象存储在free_list指向的位置。当然,最开始的时候这两个指针肯定是null。
现在我们大体可以想象python整数对象系统在内存是一种怎样的结构了。下面通过PyInt_FromLong来展示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);
return (PyObject *) v;
}
为通用整数对象池申请新空间
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;
}
PyIntObject对象的创建通过2个步骤完成:
python 是如何创建通用整数对象池的呢?我们看看源码
static PyIntObject *fill_free_list(void)
{
PyIntObject *p, *q;
**首先申请内存空间,并链接到已有的block_list中
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
if (p == NULL)
return (PyIntObject *) PyErr_NoMemory();
((PyIntBlock *)p)->next = block_list;
block_list = (PyIntBlock *)p;
**然后将PyIntBlock中的objects数组转变成单向链表
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;
}
执行流程:
转换完成之后的block如下图:
举个例子:创建2,3,4这样的整数对象,使用的是小整数对象池,创建过程如下图所示
空闲内存互连是在int_dealloc被调用时实现的,如图:
在新增4的时候,发现还有空闲空间,此时将4存储在free_list指向的为止,当释放3的内存后,free_list指向3的位置。
需要注意的是:fill_free_list不仅发生在PyInt_FromLong首次调用,在block的空闲内存被使用完时(free_list变为null)也会调用。