例题一:
以下例子中, area 计算过程中有临时对象创建吗?为什么?
>>> pi = 3.14
>>> r = 2
>>> area = pi * r ** 2
Python 如何优化临时对象创建效率?
解答:(看过上面一节讲的内容。其实这个问题的答案就已经了然于心了)
(第一问)会有临时对象创建。 这个语句首先计算半径 r 的平方,中间结果由一个临时对象来保存,假设是 t ; 然后计算圆周率 pi 与 t 的乘积,得到最终结果并赋值给变量 area ; 最后,销毁临时对象 t 。
(第二问)为了提高浮点对象创建效率, Python 引入了 空闲对象缓存池,浮点对象销毁后, Python 并不急于回收内存,而是将对象放入一个 空闲链表 。 后续需要创建浮点对象时,先到空闲链表中取,省去分配内存的开销。
解析:答案其实已经说得很清晰了,但是并没有提到C语言实现的细节。 在C语言底层实现,空闲对象缓存池 是一个 空闲链表,(1)free_list 变量,指向空闲链表 头节点 的指针;(2)numfree 变量,维护空闲链表 当前长度 ;(3)PyFloat_MAXFREELIST 宏,限制空闲链表的 最大长度 ,避免占用过多内存(最大长度源码里是 100),(4)ob_type字段 作为 next指针用
创建对象(两种方式)最终调用的都是
(1)PyFloat_FromDouble 函数通过浮点值创建浮点对象;(2)PyFloat_FromString 函数通过字符串对象创建浮点对象;
而销毁对象,实际上是调用 _Py_Dealloc 宏调用类型对象 PyFloat_Type 中的 tp_dealloc 函数指针: 实现将 对象放入 缓存池中。
另外关于空闲对象缓存池的细节,就自行参看上节。
例题二
以下例子中,变量 e 的 id 值为何与已销毁的变量 pi 相同?
>>> pi = 3.14
>>> id(pi)
4565221808
>>> del pi
>>> e = 2.71
>>> id(e)
4565221808
答案:
Python 为了优化浮点对象内存分配效率,引入了 空闲对象缓存池 。 浮点对象销毁后, Python 并不急于回收对象内存,而是将对象缓存在空闲链表中,以备后用。
例子中, pi 对象销毁后, Python 先不回收对象内存,而是将其插空闲对象链表头部。 当创建浮点对象 e 时, Python 从链表头取出空闲对象来用,省去了申请内存的开销。 换句话讲, pi 对象销毁后被 e 重新利用了,因此 id 值相同也就不奇怪了。
注释:
这一题就是缓存池的原理了,再讲下:
- 对象销毁时, Python 将其缓存在空闲链表中,以备后用。考察 float_dealloc 函数:
if (numfree >= PyFloat_MAXFREELIST) {
PyObject_FREE(op);
return;
}
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list;
free_list = op;
- 1-4 行,空闲链表长度达到限制值,调用 PyObject_FREE 回收对象内存;
- 第 5-7 行,空闲链表长度暂未达到限制,将对象插到空闲链表头部;
- 下次使用时,便会从链表头部取出使用。