您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

Python源码剖析之整数对象

2023/7/1 9:52:34发布42次查看
今天在微信公众号看到一个案例,感觉确实不错,自己试了一下。没毛病。好了,不多bb。分享给各位。
但是这篇文章分享之前我还是要推荐下我自己的python群:595266089,不管你是小白还是大牛,小编我都挺欢迎,不定期分享干货,包括我自己整理的一份2017最新的python资料和零基础入门教程,欢迎初学和进阶中的小伙伴。
在《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版本中也有,看来是一段陈年往事。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product