[python源码剖析]整数对象(一)
整数对象篇
关于整数对象,我们主要搞清楚以下几个问题:
1.整数对象是如何创建的?
2.整数对象在内存中是如何保存的?
3.整数对象是如何添加和删除的?
想要弄清楚以上三个问题,我们还得从源码来看看,究竟一个整数对象是如何从无到有的。
首先,来看看整数对象的定义:
1 typedef struct { 2 PyObject_HEAD 3 long ob_ival; 4 } PyIntObject;
整数对象定义中包含两部分:一部分是我们熟悉PyObject_HEAD,这个宏定义几乎在python所有对象中都存在,而且存在于所有对象的开始部分;第二部分是一个长整型的变量,至于这个变量有什么用,我将会在下面的来详细说明。
在来看看整型对象的类型对象源码:
PyTypeObject PyInt_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "int", sizeof(PyIntObject), 0, (destructor)int_dealloc, /* tp_dealloc */ (printfunc)int_print, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ (cmpfunc)int_compare, /* tp_compare */ (reprfunc)int_to_decimal_string, /* tp_repr */ &int_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)int_hash, /* tp_hash */ 0, /* tp_call */ (reprfunc)int_to_decimal_string, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_INT_SUBCLASS, /* tp_flags */ int_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ int_methods, /* tp_methods */ 0, /* tp_members */ int_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ int_new, /* tp_new */ (freefunc)int_free, /* tp_free */ };
在这个类型对象中,保存了许多关于整型对象的元信息。
int_dealloc | PyIntObject对象的析构操作(对象的销毁) |
int_free | PyIntObject对象的释放操作(释放内存) |
int_repr | 转化成PyStringObject对象 |
int_hash | 获得HASH值 |
int_print | 打印PyIntObject |
int_cmopare | 比较操作 |
int_as_number | 数值操作集合 |
int_methods | 成员函数集合 |
来看看个简单的例子:
static int int_compare(PyIntObject *v, PyIntObject *w) { register long i = v->ob_ival; //我们之前说过,变量的值都是存放在ob_ival里面的,这里就调用这个值 register long j = w->ob_ival; return (i < j) ? -1 : (i > j) ? 1 : 0;//如果i<j,则返回-1;否则就返回1;如果i==j,则返回0 }
在来看看两个整型变量是如何完成加法操作的。还是先上源码:
#define PyInt_AS_LONG(op) (((PyIntObject *)(op))->ob_ival)
#define CONVERT_TO_LONG(obj, lng) \ //CONVERT_TO_LONG宏定义
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); /* casts in the line below avoid undefined behaviour on overflow */ x = (long)((unsigned long)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); }
还记得我们刚刚说的那个long变量吗?从这个加法定义来看,pyintobject对象所实现的加法操作是直接在其维护的long变量上进行的,在完成了加法计算后,还检查进行了溢出检查。如果没有溢出,就返回一个PyIntObject,而这个PyIntObject所拥有的正好是加法操作的值。如果发生溢出,返回的结果不再是一个PyIntObject对象了,而是一个PyLongObject对象了。
下面就开始进入正题,来看看我们的第一个问题。
PyIntObject对象的创建和维护
- 对象创建的3种途径
PyObject *PyInt_FromLong(long ival) PyObject *PyInt_FromString(char *s, char **pend, int base) PyObject *PyInt_FromUnicode(Py_UNICODE *s, Py_ssize_t length, int base)
分别从long,string,Unicde生成Int类型的对象。这里,我们仅仅考察Long对象生成PyIntObject类型。因为PyInt_FromString和PyInt_FromUnicode实际上都是下先将字符串或者Unicode转换成浮点数,然后再调用了PyInt_FromFloat。
PyObject * PyInt_FromString(char *s, char **pend, int base) { char *end; long x; Py_ssize_t slen; PyObject *sobj, *srepr; if ((base != 0 && base < 2) || base > 36) { PyErr_SetString(PyExc_ValueError, "int() base must be >= 2 and <= 36"); return NULL; } while (*s && isspace(Py_CHARMASK(*s))) s++; errno = 0; if (base == 0 && s[0] == '0') {//将字符转化为long值 x = (long) PyOS_strtoul(s, &end, base); if (x < 0) return PyLong_FromString(s, pend, base); } else x = PyOS_strtol(s, &end, base); if (end == s || !isalnum(Py_CHARMASK(end[-1]))) goto bad; while (*end && isspace(Py_CHARMASK(*end))) end++; if (*end != '\0') { bad: slen = strlen(s) < 200 ? strlen(s) : 200; sobj = PyString_FromStringAndSize(s, slen); if (sobj == NULL) return NULL; srepr = PyObject_Repr(sobj); Py_DECREF(sobj); if (srepr == NULL) return NULL; PyErr_Format(PyExc_ValueError, "invalid literal for int() with base %d: %s", base, PyString_AS_STRING(srepr)); Py_DECREF(srepr); return NULL; } else if (errno != 0) return PyLong_FromString(s, pend, base); if (pend) *pend = end; return PyInt_FromLong(x); }
为了更好的理解对象是如何创建的,我们首先要来了解一下整数对象在内存中是如何被组织起来的。
在python内部,整数对象被分为小整数对象和大整数对象,那么何为小整数对象和大整数对象呢?
小整数对象,顾名思义就是数值比较小,而且这些整数,在程序运行期间会被频繁地使用。因此,对于这些频繁使用的小整数以及那些不经常使用的大整数,我们必须做些区别,不能采用同一套处理机制,否则这样就会浪费大量的内存空间。在python中,所有对象都是存活在堆,这也就是说,如果没有一种特殊的机制,对于这些频繁使用的小整数,python将一次又一次的在堆上申请内存,并且有一次一次的释放内存,这样不仅仅降低了运行效率,而且也会在堆上造成大量的内存碎片,严重影响python的整体性能。
针对这样的问题,python之父提出了一个解决方案:对象池技术。至于什么事对象池技术,我们在接下来的叙述中会讲到。采用了对象池技术,那么好处就来了。之前我们说过,整数对象是不可变的,再加上采用对象池技术,那么处在对象池中的整数对象不就可以共享了么。哈哈,的确是这样的,后面我们会说的引用计数,就是基于这个对象池技术产生的。但是采用这种技术,也需要考虑到一个问题,小整数到底多小才算是小整数数?它和大整数的界限在哪?从源码上来看,python提供了一种动态的方式来确定小整数对象池中到底有多少这样的小整数。
#ifndef NSMALLPOSINTS //如果没有定义,则执行下面 #define NSMALLPOSINTS 257 //规定分配257个小正整数 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 //规定分配5个小负整数 #endif #if NSMALLNEGINTS + NSMALLPOSINTS > 0 static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; //动态分配262个小整数对象 #endif
当然,你也可以通过修改源码的方式,来扩大小整数对象的个数,然后再次编译即可。
从上面我们可以看出,python将小整数的范围定在了[-5,257]以内,也就是说,在对象池中,最多也只能同时存在262个小整数对象。对于这些小整数对象,python直接将其缓存在内存中,并将其指针放在small_ints中。那么对于大整数,又是如何存储的呢?
对于其他整数,python则是使用运行环境申请的内存空间,这些内存空间,由这些大整数对象,轮流使用。下面来看源码:
#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定义中可以看到,在一个PyIntBlock中维护着N_INTOBJECTS个对象。而N_INTOBJECTS=(BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject),这样算出的结果为82个。显然这个大整数对象分配的地方也是可以调整的。这个BLOCK通过一个单链表维护,而每个block中都维护着一个PyIntObject的数组----objets。而且python内部也是通过这个block_list来讲空闲的block管理起来。这个空闲链表的表头就是free_list。当然,最开始的时候,两种均为空。
内存模型如下图所示:
接下里,我们来看看一个对象是如何添加和删除的。
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; } //申请内存 v = free_list; free_list = (PyIntObject *)Py_TYPE(v); PyObject_INIT(v, &PyInt_Type); v->ob_ival = ival; return (PyObject *) v; }
PyIntObject对象的创建分为两步:
如果小整数对象池被激活,则使用小整数对象池
如果不能使用小整数对象池,则使用通用整数对象池
代码分析:
如果NSMALLNEGINTS + NSMALLPOSINTS > 0成立,那么python认为小整数对象池被激活,那么首先会检查这个long值是否在小整数范围内,如果在范围之中,那么就好办。只要返回小整数对象池中对应的对象就可以了。如果小整数对象池没有被激活,或者出传入的long值不在小整数范围内,python就会转向block_list维护的通用整数对象池中。
当首次调用PyInt_FromLong时,free_list必定为NULL,这时python会调用fill_free_list(),从而创建新的block,从而就创建了新的空闲内存空间。需要注意的是,python对fill_free_list()调用不仅仅发生在首次调用,还会发生在空闲块用完的情况,这也会导致free_list=NULL,从而下一次调用就触发PyInt_FromLong调用fill_free_list().
static PyIntObject *fill_free_list(void) { PyIntObject *p, *q; //申请sizeof(PyIntBlock)的内存空间,并且链接到BLOCK链表中 p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock)); if (p == NULL) return (PyIntObject *) PyErr_NoMemory(); ((PyIntBlock *)p)->next = block_list; block_list = (PyIntBlock *)p; //将PyIntBlock中PyIntObjects数组--objects--转变为单链表 p = &((PyIntBlock *)p)->objects[0]; q = p + N_INTOBJECTS; while (--q > p) //链接过程 Py_TYPE(q) = (struct _typeobject *)(q-1); Py_TYPE(q) = NULL; return p + N_INTOBJECTS - 1; }
调用该函数,首先会申请一个新的PyIntBlock
这时block中的objects还仅仅是一个Pyintobject对象的数组,接下来,python将所有的objects中的pyintobject对象通过指针依次连接起来,从而将数组转变成一个单向链表。从上述代码可以看出,数组的链接是从最后一个元素开始,其中ob_type指针作为链接指针。
当block中还有剩余的内存没有被一个pyintobject占用时,free_list就不会指向NULL。因此,只有在所有内存都被占用了以后,pyint_fromLong才会再次调用fill_free_list申请新的空间。
[python源码剖析]整数对象(二):http://www.cnblogs.com/vipchenwei/articles/6933938.html