2.1 Python3 float详解
2021/11/22 20:12:38
本文主要是介绍2.1 Python3 float详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
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空闲对象缓存池的介绍,该机制对提高对象分配效率发挥着很重要的作用。
这篇关于2.1 Python3 float详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-21Python编程基础教程
- 2024-11-20Python编程基础与实践
- 2024-11-20Python编程基础与高级应用
- 2024-11-19Python 基础编程教程
- 2024-11-19Python基础入门教程
- 2024-11-17在FastAPI项目中添加一个生产级别的数据库——本地环境搭建指南
- 2024-11-16`PyMuPDF4LLM`:提取PDF数据的神器
- 2024-11-16四种数据科学Web界面框架快速对比:Rio、Reflex、Streamlit和Plotly Dash
- 2024-11-14获取参数学习:Python编程入门教程
- 2024-11-14Python编程基础入门