[绝对原创 转载请注明出处]
Python源码剖析
——PyListObject对象(3)
本文作者: Robert Chen ([email protected] )
还记得吗,刚才我们按下了一个有趣的话题。没错,就是那个缓冲池,free_list。现在,是揭开它的神秘面纱的时候呢。我们想知道的问题是:free_list中所缓冲的PyListObject对象是从哪里获得的,是在何时创建的。答案就在一个PyListObject被销毁的过程中:
[listobject.c]
static void list_dealloc(PyListObject *op)
{
int i;
PyObject_GC_UnTrack(op);
Py_TRASHCAN_SAFE_BEGIN(op)
if (op->ob_item != NULL) {
/* Do it backwards, for Christian Tismer.
There's a simple test case where somehow this reduces
thrashing when a *very* large list is created and
immediately deleted. */
i = op->ob_size;
while (--i >= 0) {
Py_XDECREF(op->ob_item[i]);
}
PyMem_FREE(op->ob_item);
}
if (num_free_lists < MAXFREELISTS && PyList_CheckExact(op))
free_lists[num_free_lists++] = op;
else
op->ob_type->tp_free((PyObject *)op);
Py_TRASHCAN_SAFE_END(op)
}
在销毁一个PyListObject的时候,当然要做的一件事是为list中的每一个元素改变其引用计数。然后,我们就来到了最有趣的部分。Python会检查我们开始提到的那个缓冲池,free_lists,查看其中缓存的PyListObject的数量是否已经满了,如果没有,就将该待删除的PyListObject放到缓冲池中,以备后用。现在一切真相大白了,那个在Python启动是空荡荡的缓冲池原来都是被本应该死去的PyListObject对象给填充了 ,在以后创建新的PyListObject的时候,Python会首先唤醒这些已经“死去”的PyListObject。感谢党,感谢政府,又给它们一个重新做“人”的机会:)但是,需要指出,这里缓冲的仅仅是PyListObject对象,而没有这个对象曾经拥有的PyObject*列表,因为这些PyObject指针的引用计数已经减少了,这些指针所指的对象都要各奔前程,或生存,或毁灭,不再被PyListObject所给与的那个引用计数所束缚。PyListObject如果继续维护一个指向这些对象的指针的列表,就可能产生悬空指针的问题。所以,PyObject*列表所占用的空间必须归还给系统。
看一下我们刚刚创建的PyListObject的最后归宿:
在下一次创建PyListObject时,这个PyListObject将重新被唤醒,重新分配PyObject*列表占用的内存,重新拥抱新的对象。放眼四周,曾经所拥有过的那些对象,有的已经容颜苍老,有的已经烟消云散,是否有一种“无私人非事事休,欲语泪先流”的感慨呢?:)
首先我们来观察在PyListObject中维护的元素数量变化时,PyListObject中ob_size和allocated两个变量的变化情况,从中窥见PyListObject对内存的使用和管理。
在PyListObject的输出操作list_print中,我们添加了如下代码,以观察PyListObject对内存的管理:
printf("/nallocated=%d, ob_size=%d/n", op->allocated, op->ob_size);
观察结果如图9所示:
首先创建一个包含一个元素的list,这时ob_size和allocated都是1。这时list中拥有的所有内存空间都已被使用完,所以下次插入元素时就一定会调整list的内存空间了。
随后向list末尾追加元素2,可以看到,调整内存空间的动作发生了。allocated变成了5,而ob_size则变成了2,这里明确地显示出了PyListObject所采用的与C++中vector一样的内存缓冲池策略。
继续向list末尾追加元素3,4,5,当追加了元素5之后,list所拥有的内存空间又被使用完了,下一次再追加或插入元素时,内存空间调整的动作又会再一次发生。
如果这时在list中删除元素3,可以看到,ob_size发生了变化,而allocated则不发生变化,它始终如一地维护着当前list所拥有的全部内存数量。
接下来我们从图10的结果中观察一下PyListObject对象的创建和删除对于Python维护的PyListObject对象缓冲池的影响。
这次为了消除Python交互环境执行时对PyListObject对象缓冲池的影响,我们通过执行py脚本文件来观察,可以看到,当创建新的PyListObject对象时,如果缓冲池中有可用的PyListObject对象,则会使用缓冲池中的对象;而在销毁一个PyListObject对象时,确实将这个对象放到了缓冲池中。