Python 源码阅读 - int
==========================
代码我也仅仅是粗粗读了一遍, 可能出现疏漏和理解错误, 发现了望指出哈.
今天面了一家靠谱的创业公司, 可惜不是Python向的, 想继续玩Python是有代价的, 选择余地太窄了……
话说写文章很耗时间, 这个花了两个多小时….主要还是自个绘图渣效率低:(
准备找工作事宜很占时间, 后面只能慢慢来了(好像还很多很多的样子)
示例
>>> a = 1
>>> b = 1
>>> id(a) == id(b)
True
>>> c = 257
>>> d = 257
>>> id(c) == id(d)
False
#在python2.x中, 对于大的序列生成, 建议使用xrange(100000) 而不是range(100000), why?
源码位置 Include/intobject.h | Objects/intobject.c
PyIntObject
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
结构
几个构造方法
# 从字符串, 生成PyIntObject对象
PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int);
# 从Py_UNICODE, 生成PyIntObject对象
#ifdef Py_USING_UNICODE
PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
#endif
# 从long值, 生成PyIntObject对象
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t);
PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t);
这几个方法, 只需要关注
# 因为大家最后都调用这个方法完成对象生成
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
具体的构造方法 PyInt_FromLong
这个方法的定义
PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
/* MARK: 如果, 值在小整数范围内, 直接从小整数对象池获取得到对象 */
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
/* MARK: small_ints是什么后面说 */
v = small_ints[ival + NSMALLNEGINTS];
// 引用+1
Py_INCREF(v);
/* 这里先忽略, 计数 */
#ifdef COUNT_ALLOCS
if (ival >= 0)
quick_int_allocs++;
else
quick_neg_int_allocs++;
#endif
// 返回
return (PyObject *) v;
}
#endif
// 如果free_list还不存在, 或者满了
if (free_list == NULL) {
// 新建一块PyIntBlock, 并将空闲空间链表头部地址给free_list
if ((free_list = fill_free_list()) == NULL)
// 如果失败, 返回
return NULL;
}
// 从free_list分出一个位置存放新的整数
/* Inline PyObject_New */
// 使用单向链表头位置
v = free_list;
// free_list指向单向链表下一个位置
free_list = (PyIntObject *)Py_TYPE(v);
// 初始化对象, 类型为PyInt_type, 值为ival
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
// 返回
return (PyObject *) v;
}
注意这里的Py_TYPE()
方法, 在我们第一篇文章里面有提到, 不知道的回去复习下对象的数据结构
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
简而言之:
1. 先判断数值是否是小整数, 是的话从小整数对象池里面直接返回
(这个池固定大小, 下一点讲)
2. 如果不是, 从通用整数对象池里面取一个, 初始化返回
(如果这时候通用整数对象池还不存在或者已经满了, 新建一个池加入维护. 通用整数对象池后面讲)
小整数对象池
先看定义
#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
其实, 小整数对象池就是一个PyIntObject指针
数组(注意是指针数组), 大小=257+5=262, 范围是[-5, 257)
注意左闭右开. 即这个数组包含了262个指向PyIntObject的指针.
结构
创建整数时, 如果在[-5, 257)范围, 直接返回已经存在的整数对象指针, 所以我们看到开头的例子, id比较一个true/一个false
小整数对象池, 在一开始就初始化了, 其初始化代码
int
_PyInt_Init(void)
{
PyIntObject *v;
int ival;
// 注意这里, free_list再次出现
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
// 循环, 逐一生成
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
if (!free_list && (free_list = fill_free_list()) == NULL)
return 0;
// 注意这段代码, 和上面PyInt_FromLong那段代码一样的
/* PyObject_New is inlined */
v = free_list;
free_list = (PyIntObject *)Py_TYPE(v);
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
// 放到数组里
small_ints[ival + NSMALLNEGINTS] = v;
}
#endif
return 1;
}
代码很眼熟吧, 觉得不眼熟回上面看代码
结论
1. 小整数对象池缓存 [-5, 257) 内的整数对象, 数值在这个范围的整数对象有且只存在一个...
2. 小整数对象池, 只是一个指针数组, 其真正对象依赖通用整数对象池
通用整数对象池1 - 基础结构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;
回忆一下PyIntObject
结构(1个int, 1指针, 1个long), size=4+4+4(先这么算), N_INTOBJECTS = 82
结构
通用整数对象池2 - 创建过程及运行时结构
有两个指针
# 指向一个block
static PyIntBlock *block_list = NULL;
# 指向一个PyIntObject
static PyIntObject *free_list = NULL;
生成过程的定义
// 初始化一个PyIntBlock
static PyIntObject *
fill_free_list(void)
{
PyIntObject *p, *q;
// 建立一个新的block
/* Python's object allocator isn't appropriate for large blocks. */
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
// 建立失败(内存耗光了)
if (p == NULL)
return (PyIntObject *) PyErr_NoMemory();
// block_list指向新的PyIntBlock节点
((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=block里面 PyIntObjects数组头地址, q是尾地址
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
// 从尾部开始向首部移动, 利用对象里的ob_type指针(相当于使用这个字段, ob_type不作为原来的用途), 建立起一个单向链表
// 这个单向链表的头部是数组的最后一个
while (--q > p)
Py_TYPE(q) = (struct _typeobject *)(q-1);
Py_TYPE(q) = NULL; // 单向链表最后一个元素的next指向null
// 返回单向链表的头地址!!!
return p + N_INTOBJECTS - 1;
}
新建第一个时, 只有一个
从里面拿整数时, 取free_list
指向的节点, 然后free_list
指向链表下一个节点
当一个block用完了之后, 即free_list=NULL
, 此时要新建另一个PyIntBlock
新建第二个
通用整数对象池3 - 删除一个整数时
定义
#define PyInt_CheckExact(op) ((op)->ob_type == &PyInt_Type)
static void
int_dealloc(PyIntObject *v)
{
// 是整数类型, 将对象放入free_list单向链表头
if (PyInt_CheckExact(v)) {
Py_TYPE(v) = (struct _typeobject *)free_list;
free_list = v;
}
else
Py_TYPE(v)->tp_free((PyObject *)v); //不是整数类型, 对应类型析构
}
可以看到, 回收的时候, 把空间给放回到free_list
了, 后面接着用
block_list
维护着所有PyIntBlock
列表, 查看源码注释可以看到
PyIntBlocks are never returned to the
system before shutdown (PyInt_Fini).
即, PyIntBlock
申请的所有内存, 在Python结束之前, 都不会被释放
所以, 使用range(100000), 运行后, 虽然程序结束了, 但是整数占用空间还在.
建议对大范围的序列生成使用xrange
python3.x不用担心这个问题
changelog
2014-08-07 first version