Python整数对象实现原理详解


Posted in Python onJuly 01, 2019

整数对象在Python内部用PyIntObject结构体表示:

typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;

PyObject_HEAD宏中定义的两个属性分别是:

int ob_refcnt; 
struct _typeobject *ob_type;

这两个属性是所有Python对象固有的:

  • ob_refcnt:对象的引用计数,与Python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制
  • ob_type:用于描述Python对象的类型信息。

由此看来PyIntObject就是一个对C语言中long类型的数值的扩展,出于性能考虑,对于小整数,Python使用小整数对象池small_ints缓存了[-5,257)之间的整数,该范围内的整数在Python系统中是共享的。

#define NSMALLPOSINTS 257
#define NSMALLNEGINTS 5
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

Python整数对象实现原理详解

而超过该范围的整数即使值相同,但对象不一定是同一个,如下所示:当a与b的值都是10000,但并不是同一个对象,而值为1的时候,a和b属于同一个对象。

>>> a = 10000
>>> b = 10000
>>> print a is b
False
>>> a = 1
>>> b = 1
>>> print a is b
True

对于超出了[-5, 257)之间的其他整数,Python同样提供了专门的缓冲池,供这些所谓的大整数使用,避免每次使用的时候都要不断的malloc分配内存带来的效率损耗。这块内存空间就是PyIntBlock。

struct _intblock {
struct _intblock *next;
PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

这些内存块(PyIntBlock)通过一个单向链表组织在一起,表头是block_list,表头始终指向最新创建的PyIntBlock对象。

PyIntBlock有两个属性:next,objects。next指针指向下一个PyIntBlock对象,objects是一个PyIntObject数组(最终会转变成单向链表),它是真正用于存储被缓存的PyIntObjet对象的内存空间。

free_list单向链表是所有PyIntBlock内存块中空闲的内存。所有空闲内存通过一个链表组织起来的好处就是在Python需要新的内存来存储新的PyIntObject对象时,能够通过free_list快速获得所需的内存。

Python整数对象实现原理详解

创建一个整数对象时,如果它在小整数范围内,就直接从小整数缓冲池中直接返回,如果不在该范围内,就开辟一个大整数缓冲池内存空间:

[intobject.c]
PyObject* PyInt_FromLong(long ival)
{
register PyIntObject *v; 
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
//[1] :尝试使用小整数对象池
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
return (PyObject *) v;
}
#endif
//[2] :为通用整数对象池申请新的内存空间
if (free_list == NULL) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
//[3] : (inline)内联PyObject_New的行为
v = free_list;
free_list = (PyIntObject *)v->ob_type;
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}

fill_free_list就是创建大整数缓冲池内存空间的逻辑,该函数返回一个free_list链表,当整数对象ival创建成功后,free_list表头就指向了v->ob_type,ob_type不是所有Python对象中表示类型信息的字段吗?怎么在这里作为一个连接指针呢?这是Python在性能与代码优雅之间取中庸之道,对名称的滥用,放弃了对类型安全的坚持。把它理解成指向下一个PyIntObject的指针即可。

[intobject.c]
static PyIntObject* fill_free_list(void)
{
PyIntObject *p, *q;
// 申请大小为sizeof(PyIntBlock)的内存空间
// block list始终指向最新创建的PyIntBlock
p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
((PyIntBlock *)p)->next = block_list;
block_list = (PyIntBlock *)p;
//:将PyIntBlock中的PyIntObject数组(objects)转变成单向链表
p = &((PyIntBlock *)p)->objects[0];
q = p + N_INTOBJECTS;
while (--q > p)
// ob_type指向下一个未被使用的PyIntObject。
q->ob_type = (struct _typeobject *)(q-1);
q->ob_type = NULL;
return p + N_INTOBJECTS - 1;
}

不同的PyIntBlock里面的空闲的内存是怎样连接起来构成free_list的呢?这个秘密放在了整数对象垃圾回收的时候,在PyIntObject对象的tp_dealloc操作中可以看到:

[intobject.c]
static void int_dealloc(PyIntObject *v)
{
if (PyInt_CheckExact(v)) {
v->ob_type = (struct _typeobject *)free_list;
free_list = v;
}
else
v->ob_type->tp_free((PyObject *)v);
}

原来PyIntObject对象销毁时,它所占用的内存并不会释放,而是继续被Python使用,进而将free_list表头指向了这个要被销毁的对象上。

总结

  • Python中的int对象就是c语言中long类型数值的扩展
  • 小整数对象[-5, 257]在python中是共享的
  • 整数对象都是从缓冲池中获取的。
  • 整数对象回收时,内存并不会归还给系统,而是将其对象的ob_type指向free_list,供新创建的整数对象使用

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python的内存泄漏及gc模块的使用分析
Jul 16 Python
简单介绍Python的轻便web框架Bottle
Apr 08 Python
Python彩色化Linux的命令行终端界面的代码实例分享
Jul 02 Python
Python实现将SQLite中的数据直接输出为CVS的方法示例
Jul 13 Python
python连接数据库的方法
Oct 19 Python
python 读写文件,按行修改文件的方法
Jul 12 Python
Python中判断子串存在的性能比较及分析总结
Jun 23 Python
python 实现两个npy档案合并
Jul 01 Python
Python连接mysql数据库及简单增删改查操作示例代码
Aug 03 Python
用Python制作mini翻译器的实现示例
Aug 17 Python
python把一个字符串切开的实例方法
Sep 27 Python
Django debug为True时,css加载失败的解决方案
Apr 24 Python
python实现两个dict合并与计算操作示例
Jul 01 #Python
Python字符串对象实现原理详解
Jul 01 #Python
Python转换时间的图文方法
Jul 01 #Python
Python列表对象实现原理详解
Jul 01 #Python
win8.1安装Python 2.7版环境图文详解
Jul 01 #Python
Python为何不能用可变对象作为默认参数的值
Jul 01 #Python
浅析Python与Mongodb数据库之间的操作方法
Jul 01 #Python
You might like
PHP 文件扩展名 获取函数
2009/06/03 PHP
让PHP开发者事半功倍的十大技巧小结
2010/04/20 PHP
Yii2 rbac权限控制之菜单menu实例教程
2016/04/28 PHP
PHP封装的PDO数据库操作类实例
2017/06/21 PHP
PHP与Web页面的交互示例详解一
2020/08/04 PHP
window.addeventjs事件驱动函数集合addEvent等
2008/02/19 Javascript
javascript中的关于类型转换的性能优化
2010/12/14 Javascript
javascript生成随机颜色示例代码
2014/05/05 Javascript
Blocksit插件实现瀑布流数据无限( 异步)加载
2014/06/20 Javascript
JavaScript函数定义的常见注意事项小结
2014/09/16 Javascript
Bootstrap滚动监听(Scrollspy)插件详解
2016/04/26 Javascript
Vue.js每天必学之构造器与生命周期
2016/09/05 Javascript
利用JQuery操作iframe父页面、子页面的元素和方法汇总
2017/09/10 jQuery
Vue实现带进度条的文件拖动上传功能
2018/02/23 Javascript
Angular学习笔记之集成三方UI框架、控件的示例
2018/03/23 Javascript
js使用ajax传值给后台,后台返回字符串处理方法
2018/08/08 Javascript
vue中各选项及钩子函数执行顺序详解
2018/08/25 Javascript
vue自定义指令用法经典实例小结
2019/03/16 Javascript
[04:00]黄浦江畔,再会英雄——完美世界DOTA2 TI9应援视频
2019/07/31 DOTA
Python中的tuple元组详细介绍
2015/02/02 Python
对于Python异常处理慎用“except:pass”建议
2015/04/02 Python
用Django实现一个可运行的区块链应用
2018/03/08 Python
python如何修改装饰器中参数
2018/03/20 Python
python如何通过实例方法名字调用方法
2018/03/21 Python
使用Python+wxpy 找出微信里把你删除的好友实例
2019/02/21 Python
Python Web框架之Django框架cookie和session用法分析
2019/08/16 Python
Python爬虫爬取电影票房数据及图表展示操作示例
2020/03/27 Python
Django更新models数据库结构步骤
2020/04/01 Python
MIS软件工程师的面试题
2016/04/22 面试题
关于建议书的格式范文
2014/05/20 职场文书
刑事代理授权委托书
2014/09/17 职场文书
意外伤害赔偿协议书范文
2014/09/23 职场文书
教师节大会主持词
2015/07/06 职场文书
SQLServer2008提示评估期已过解决方案
2021/04/12 SQL Server
详解SpringBoot异常处理流程及原理
2021/06/21 Java/Android
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
2022/06/21 Golang