float内部结构
首先在文件Include/floatobject.h中,找到了float实例对象的结构体:
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
除了定长对象的共用头部,只有一个字段ob_fval,这个字段就是用来存储浮点对象的浮点值的。
在回顾一下float类型对象的结构体。float类型对象是系统内置的类型对象,是全局唯一的,因此可以作为全局变量定义。在文件Objects/floatobject.c中:
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
(destructor)float_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
(reprfunc)float_repr, /* tp_repr */
&float_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)float_hash, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
_Py_TPFLAGS_MATCH_SELF, /* tp_flags */
float_new__doc__, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
float_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
float_methods, /* tp_methods */
0, /* tp_members */
float_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 */
float_new, /* tp_new */
.tp_vectorcall = (vectorcallfunc)float_vectorcall,
};
PyFloat_Type保存了float对象的元信息,这些元信息决定了浮点实例对象的生死和行为,关键字段如下:
- tp_name:类型的名称,这里是常量'float';
- tp_dealloc、tp_init、tp_alloc、tp_new:对象创建和销毁的相关函数;
- tp_repr:生成语法字符串表示形式的函数;
- tp_str:生成普通字符串表示形式的函数;
- tp_as_number:数值操作集;
- tp_hash:哈希值生成函数;
float实例的创建
float实例对象的创建流程前面的章节已经介绍过了,再来回顾一下使用通用流程创建对象的过程:Python执行的是type类型对象当中的tp_call函数。tp_call函数进而调用float类型对象的tp_new和tp_init函数创建实例对象并进行初始化。
在源码中,PyFloat_Type的tp_init函数指针为空,这是因为float是一种很简单的对象,初始化操作就是一个赋值语句,在tp_new中完成即可。
除了通用流程,Python为内置对象实现了对象创建API,简化调用,提高效率。比如直接创建浮点对象:
>>> pi = 3.14
这里其实是通过PyFloat_FromDouble函数实现的,直接将浮点值创建成浮点对象:
PyObject *
PyFloat_FromDouble(double fval)
{
PyFloatObject *op = free_list;
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op);
numfree--;
} else {
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
if (!op)
return PyErr_NoMemory();
}
/* Inline PyObject_New */
(void)PyObject_INIT(op, &PyFloat_Type);
op->ob_fval = fval;
return (PyObject *) op;
}
- 首先为对象分配内存空间(PyObject_MALLOC函数),优先使用空闲对象缓存池。
- 初始化对象类型字段ob_type以及引用计数字段ob_refcnt(PyObject_INIT);
- 将ob_fval字段初始化为浮点值。
float实例的销毁
当对象的某次引用被解除时,Python通过Py_DECREF或者Py_XDECREF宏减少引用计数;当引用计数降为0时,Python通过_Py_Dealloc宏回收对象。
_Py_Dealloc宏调用类型对象PyFloat_Type中的tp_dealloc函数指针:
#define _Py_Dealloc(op) ( \
_Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA \
(*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))
根据代码可知,float回收对象实际调用的函数是float_dealloc:
static void
float_dealloc(PyFloatObject *op)
{
if (PyFloat_CheckExact(op)) {
if (numfree >= PyFloat_MAXFREELIST) {
PyObject_FREE(op);
return;
}
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list;
free_list = op;
}
else
Py_TYPE(op)->tp_free((PyObject *)op);
}
总结一下,float实例对象从创建到销毁整个生命周期所涉及的关键函数、宏以及调用关系如下:
空闲对象缓存池
浮点运算是比较常见的运算方式之一。其实浮点运算背后涉及大量临时对象创建和销毁的动作,比如计算圆周率:
>>> area = pi * r ** 2
该语句首先计算r**2,即半径的平方,中间结果由一个临时对象来保存,假如是变量a,然后计算圆周率pi和a的乘积,将最后的结果赋值给变量area,最后,销毁临时对象a。
可见这样一条简单的浮点运算就隐藏了一个临时对象的创建和销毁,如果是复杂的数据运算将涉及大量的对象的创建和销毁,而这就意味着大量的内存分配和回收操作,这是及其耗性能的。
Python考虑了这种情况,在销毁浮点对象后,并没有立刻回收内存,而是将对象放入一个空闲链表中,后续需要创建浮点对象时,可以先从空闲链表中取,省去了分配内存的开销。
在文件Objects/floatobject.c中可以看到浮点对象空间链表的定义:
#ifndef PyFloat_MAXFREELIST
#define PyFloat_MAXFREELIST 100
#endif
static int numfree = 0;
static PyFloatObject *free_list = NULL;
- free_list变量,指向空闲链表头节点的指针;
- numfree变量,维护空闲链表 当前长度;
- PyFloat_MAXFREELIST宏,限制空闲链表的最大长度,避免占用过多的内存;
为了不添加额外的链表指针,free_list把ob_type字段当做next指针来用,将空闲对象串成链表;
以PyFloat_FromDouble为例:
PyFloatObject *op = free_list;
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op);
numfree--;
} else {
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
// ...
}
分配内存的流程如下:
- 检查free_list是否为空;
- 如果free_list非空,则取出头节点备用,并将numfree减一,并通过Py_TYPE函数(获取对象的类型对象)取出free_list头部的ob_type字段(即第二个空闲对象的地址),将free_list指针指向新的头部;
- 如果free_list为空,则调用PyObject_MALLOC分配内存。
如此,每当需要创建浮点对象时,可以从链表中取出空闲对象,省去申请内存的开销。而当float对象被销毁时,Python将其缓存在空闲链表中,以备后用,代码如下:
if (numfree >= PyFloat_MAXFREELIST) {
PyObject_FREE(op);
return;
}
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list;
free_list = op;
主要流程便是判断空闲链表长度是否达到了限制值,如果达到了,则直接回收对象内存,如果未达到,则将对象插到空闲链表头部,并使得numfree加一。
以上部分便是Python空闲对象缓存池的介绍,该机制对提高对象分配效率发挥着很重要的作用。