专栏首页Python中文社区Python源码剖析之整数对象

Python源码剖析之整数对象

專 欄

松直,Python中文社区专栏作者

专栏地址:

http://www.zhihu.com/people/songzhili?utm_source=qq&utm_medium=social

在《Python源码剖析》中,Python的版本为2.5,而在Python3中,前面提到,int类型的底层实现是Python2中的long类型。所以,我会在本章中,先介绍Python2源码中int类型的实现,再在最后介绍一下Python3.6中int(也就是以前的long)在底层的实现。之所以这样做的原因后面会解释。

在此之前,我们先来看一个有趣但没什么卵用的现象:

想知道为什么?看完这章就知道了。

初识PyIntObject对象

Python中对整数的概念的实现是通过PyIntObject来完成的。我们曾经说过它是一个定长对象,与之相对的还有变长对象。这样的分法对我们理解Python源码有帮助,但在Python语言的层面上,我们通常还使用一种二分法,即根据对象维护数据的可变性将对象分为可变对象(mutable)和不可变对象(immutable)。Python2中的PyIntObject是一个定长对象,而PyLongObject是一个变长对象,但它们都是不可变对象。也就是说,一旦创建了它们之后,就不能改变它们的值了。

我们来看一下在Python2.7中,PyIntObject的实现:

Python2中的整数对象PyIntObject实际上就是对C原生类型long的一个包装。

我们知道关于一个Python对象的大多数元信息是保存在它的类型对象中的,对于PyIntObject是PyInt_Type:

PyInt_Type中保存了关于PyIntObject的许多元信息,它定义了一个PyIntObject支持的操作,占用的内存大小等等。其中重要的内容我们会在下面叙述。

PyIntObject的创建和维护

创建对象的途径

在intobject.h中,Python提供了几种创建PyIntObject的途径:

事实上,前两种方法都是先将字符串转换为浮点数,再调用PyInt_FromFloat来实现的。

小整数对象

我们来思考一下,在Python内部,整数对象是如此广泛地被使用,尤其是那些比较小的整数。短短几秒之间,我们可能就要用的它们成千上万次,如果我们在创建它们的时候使用malloc来请求分配内存,删除它们时再调用free来释放内存。显然,这样的性能水平是不可能达到我们的要求的,而且也会造成极大的不必要的浪费。

于是,在Python内部,对于小整数使用了对象池技术。

我们可以看出,在Python2.7中,“小整数”的定义是[-5,256],而这个指向一个整数对象数组的指针small_ints就是这个对象池机制的核心。如果我们想要重新定义“小整数”怎么办?简单,我们可以修改源代码并重新编译。

对于小整数对象,Python直接把它们缓存在小整数对象池中,用于共享。那么大整数呢?肯定不可能都缓存在内存中,但是说不定某些大整数在某个时刻会变得十分常用,不过谁也不知道究竟是哪个数字。这时候,Python选择了另一种策略。

大整数对象

Python的设计者的策略是:对于小整数对象,直接把它们全部缓存在对象池中。对于其他整数,Python运行环境将会提供一块内存空间,这块内存空间由这些对象轮流使用。

在Python中,有一个PyIntBlock结构,它被用来实现这个机制。

PyIntBlock的单向列表通过block_list维护,每一个block中都维护了一个PyIntObject数组——objects,这就是真正用于存储被缓存的PyIntObject对象的内存。我们可以想象,在运行的某个时刻,这块内存中一定有一部分被使用,而有一部分是空闲的。这些空闲状态的内存需要被组织起来,以供Python在需要存储新的整数对象时使用。Python使用一个单向链表来管理全部block的objects中的空闲内存,这个链表的表头就是free_list。在一开始,block_list和free_list都指向NULL。

添加和删除

我们来看一下PyIntObject是如何从无到有地产生,又是如何消失的。

使用小整数对象池

如果NSMALLNEGINTS + NSMALLPOSINTS > 0成立,说明小整数对象池机制被激活了,然后Python会检查传入的long值是否是小整数。如果是,就直接返回对象池中的小整数对象就可以了。如果不是,那么会转向通用整数对象池。Python会在block的objects中寻找一块可用于存储新的PyIntObject的内存,这个任务需要free_list来完成。

创建通用整数对象池

显然,当首次调用PyInt_FromLong时,free_list必定为NULL,这时Python会调用fill_free_list创建新的block。在Python运行期间,只要所有block的空闲内存被使用完了,也就是free_list指向NULL,那么下一次调用PyInt_FromLong时就会再次激活对fill_free_list的调用。

在这个函数中,会首先申请一个新的PyIntBlock结构,这时block中的objects还只是一个PyIntObject对象数组。接下来,Python将objects中的所有PyIntObject对象通过指针依次连接起来从而将数组转变成一个单向链表。z在整个链接过程中,Python使用PyObject的ob_type指针作为连接指针。当链表转换完成后,free_list也就出现在出现在它该出现的位置了。从free_list开始,沿着ob_type指针,就可以遍历刚刚创建的所有为PyIntObject准备的内存了。

说完了PyIntObject的创建,我们再来看看它的删除。在Python中,当一个对象引用计数变为0 时,Python就会着手将这个对象销毁。不同类型的对象在销毁时执行的动作也是不同的,其在与对象对应的类型对象中被定义——也就是tp_dealloc。

首先,Python会检查传入的对象是否真的是一个PyIntObject对象,如果是的话,那么将其链入free_list所维护的自由内存链表中,以供将来别的PyIntObject使用。如果只是整数对象的派生类型,那么简单地调用派生类型中指定的tp_free。

使用通用整数对象池

在Python运行的过程中,会不只有一个PyIntBlock存在于同一个链表中,但是它们维护的objects却是分离的,之间没有联系。我们设想一下,有两个PyIntBlock对象,PyIntBlock1和PyIntBlock2,前者已经被填满,后者还有空闲的空间。所以此时free_list指向的是PyIntBlock2.objects中的空闲的内存块。当前者维护的objects中有PyIntObject被删除了,这时前者出现了一块空闲的内存。那么下次创建新的PyIntObject对象时应该使用这块空闲内存。否则就意味着所有的内存只能使用一次,这和内存泄漏没什么两样。

怎么才能将空闲的内存再交由Python使用呢?关键就在于前面我们分析的PyIntObject的删除操作,通过int_dealloc中的操作,所有的PyIntBlock的objects中的空闲内存块都被链接在一起了。它们形成了一个单向链表,表头正是free_list。

小整数对象池的初始化

现在关于Python的整数对象机制还剩最后一个问题。小整数对象池是在什么时候被初始化的呢?

我们可以看到,通过_PyInt_Init的调用,Python创建了这些小整数对象,然后它们就会在整个运行周期中一直存在,直至解释器毁灭。

Python3中int的实现

int即long

我们在之前提到,在Python3中int底层实现就是以前Python2中的long类型。空口无凭,我们来看代码:

我们可以看到,PyLong_Type类型对象的tp_name就是int,也就是说,在Python内部,它就是int类型。

之所以我们在一开始不介绍Python3中的整数实现,是因为在Python3中没有了通用的整数对象池(至少我没有找到),不过还保留着小整数对象池。同时对于那些比较小也就是对于之前Python2中的long类型大材小用的整数来说,也有一个更加快速且节省资源的创建方式。

而且我还发现了一个彩蛋,就是在longobject.c中第二行的注释:

哈哈哈,官方吐槽,最为致命。而且这个注释在Python2.7版本中也有,看来是一段陈年往事。

本文为作者原创作品,未经作者授权同意禁止转载

本文分享自微信公众号 - Python中文社区(python-china)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-07-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 用Python研究了三千套房子,告诉你究竟是什么抬高了房价?

    关于房价,一直都是全民热议的话题,毕竟不少人终其一生都在为之奋斗。 房地产的泡沫究竟有多大不得而知?今天我们抛开泡沫,回归房屋最本质的内容,来分析一下房价的影...

    Python中文社区
  • 每天一个Linux命令:tail

    tail命令:显示文件结尾 用途 显示文件的最后几行。 语法 标准语法 tail [ -f ] [ -c Number | -n Number | -m Num...

    Python中文社区
  • 腾讯云北京技术沙龙邀请函:AI技术全面场景化落地实践

    随着相关技术的突破发展以及国家产业政策的大力扶持,人工智能技术以及应用实践已然出现了向上的趋向。作为未来科技发展的重要方向和必然趋势,AI 技术将深入人们生活的...

    Python中文社区
  • 三十万次Full GC

    今天测试同学反馈API耗时很长,超过3秒的比例很高。 查看日志发现,小部分请求耗时比较大,约2秒左右,但是比例不高,与反馈比例有点不一致。后来发现是有一台服务器...

    十毛
  • 学习 | 爬虫告诉你, 互联网大数据行业有多赚钱

    随着互联网大数据行业的日渐兴盛,越来越多的人投身其中,也有很多的朋友对此有着浓厚的兴趣,想要投身其中。本期我们带大家走进互联网大数据行业,了解数据挖掘&机器学习...

    灯塔大数据
  • Redis 基本数据结构三:哈希

    几乎所有的编程语言都提供了哈希(hash)类型,例如 Java 中的 Map,python 中的字典,在Redis中,哈希类型是指键的值本身又是一个键值对结构,...

    CoderJed
  • C语言中的预处理

    1、 宏定义 预处理命令可以改变程序设计环境,提高编程效率,它们并不是 C 语言本身的组成部分,不能直接对 它们进行编译,必须在对程序进行编译之前,先对程序中...

    编程范 源代码公司
  • 开源一个小程序,还教部署那种。

    之前有给小伙伴们承诺过一个开源一个小程序,今天开始开源一个部署上线的打卡小程序:一见打卡。

    月小水长
  • 基因芯片数据分析(一):芯片数据初探

    简单地讲,基因芯片就是一系列微小特征序列的(通常是DNA探针,也可能是蛋白质)的集合,它们可以被用于定性或者定量检查样品内特异分子的成份。比如说,基因芯片可以检...

    DoubleHelix
  • 准确率超 99% 的病理 AI 系统,能否成为“两癌筛查”的一把利器?

    大量、优质病理医生的紧缺,让基层的两癌筛查工作进展异常缓慢。业界的一个测算结果是,如果要完成全国3.51亿适龄妇女的两癌筛查,以目前人工的方式需要18年。这个数...

    AI掘金志

扫码关注云+社区

领取腾讯云代金券