Python内存管理方式和垃圾回收算法解析


Posted in Python onNovember 11, 2017

概要

在列表,元组,实例,类,字典和函数中存在循环引用问题。有 __del__ 方法的实例会以健全的方式被处理。给新类型添加GC支持是很容易的。支持GC的Python与常规的Python是二进制兼容的。

分代式回收能运行工作(目前是三个分代)。由 pybench 实测的结果是大约有百分之四的开销。实际上所有的扩展模块都应该依然如故地正常工作(我不得不修改了标准发行版中的 new 和 cPickle 模块)。一个叫做 gc 的新模块马上就可以用来调试回收器和设置调试选项。

回收器应该是跨平台可移植的。Python 的补丁版本通过了所有的回归测试并且跑 Grail、Idle 和 Sketch 的时候没有任何问题。

自 Python 2.0 和之后的版本,可移植的垃圾回收机制已经包括在其中了。垃圾回收默认是开启的。请高兴些吧!

为什么我们需要垃圾回收?

目前版本的 Python 采用引用计数的方式来管理分配的内存。Python 的每个对象都有一个引用计数,这个引用计数表明了有多少对象在指向它。当这个引用计数为 0 时,该对象就释放了。引用计数对于多数程序都工作地很好。然而,引用计数有一个本质上的缺陷,是由于循环引用引起的。循环引用最简单的例子就是一个引用自身的对象。比如:

>>> l = []
>>> l.append(l)
>>> del l

这个创建的列表的引用计数现在是 1。然而,因为它从 Python 内部已经无法访问,并且可能没法再被用到了,它应该被当作垃圾。在目前版本的 Python 中,这个列表永远不会被释放。

一般情况下循环引用不是一个好的编程实践,并且几乎总该被避免。然而,有时候很难避免制造循环引用,要么则是程序员甚至没有察觉到循环引用的问题。对于长期运行的程序,比如服务器,这个问题特别令人烦恼。人们可不想他们的服务器因为循环引用无法释放访问不到的对象而耗尽内存。对于大型程序,很难发现循环引用是怎么创造出来的。

“传统的”垃圾回收是怎样的?

传统的垃圾回收(比如标记-清除法或者停止-拷贝法)通常工作如下:

找到系统的根对象。根对象就像是全局的环境(比如 Python 中的 __main__ 模块)和堆栈上的对象。
从这些对象搜索所有的可以访问的对象。这些对象都是“活跃”的。
释放其他所有对象。
不幸的是这个方法不能用于当前版本的 Python。由于扩展模块的工作方式,Python 不能完全地确定根对象集合。如果根对象集合没法被准确地确定,我们就有释放仍然被引用的对象的风险。即使用其他方式设计扩展模块,也没有可移植的方式来找到当前 C 堆栈上的对象。而且,引用计数提供了一些 Python 程序员已然期待的有关局部性内存引用和终结语义的好处。最好是我们能够找到一个即能使用引用计数,又能够释放循环引用的的办法。

这个方法如何工作?

从概念上讲,这个方法与传统垃圾回收机制相反。这个方法试图去找到所有的不可访问对象,而不是去找所有的可访问对象。这样更加安全,因为如果这个算法失败了,起码不会比不进行垃圾回收还要糟(不考虑我们浪费掉的时间和空间)。

因为我们仍然在用引用计数,垃圾回收器只需要找到循环引用。引用计数会处理其他类型垃圾。首先我们观察到循环引用只能被容器对象创造。容器对象是可以包含其他对象的引用的对象。在Python中,列表、字典、实例、类和元祖都是容器对象的例子。整数和字符串不是容器。通过这个发现,我们意识到非容器对象可以被垃圾回收忽略。这是一个有用的优化因为整数和字符串这样的应该比较轻快。

现在我们的想法是记录所有的容器对象。有几种方法可以做到,然而最好的一种办法是利用双向链表,链表中的对象结构中包含指针字段。这样就可以使对象从集合中快速插入删除,而且不需要额外内存空间分配。当一个容器被创建,它就插入这个集合,被删除时,就从集合中去除。

既然我们能够得到所有的容器对象,我们怎么找到循环引用呢?首先我们往容器对象中添加两个指针外的另一个字段。我们命名这个字段 gc_refs。通过以下几步我们可以找到循环引用:

对每个容器对象,设 gc_refs 的值为对象的引用计数。
对每个容器对象,找到它引用的其他容器对象并把它们的 gc_refs 值减一。
所有的 gc_refs 大于 1 的容器对象是被容器对象集合外的对象所引用的。我们不能释放这些对象,所以我们把这些对象放到另一个集合。
被移走的对象所引用的对象也不能被释放。我们把它们和它们能访问到的对象都从目前集合移走。
在目前集合中的剩下的对象是仅被该集合中对象引用的(也就是说,他们无法被 Python 取到,也就是垃圾)。我们现在可以去释放这些对象。

Finalizer的问题

我们的宏伟计划还有一个问题,就是使用 finalizer 的问题。Finalizer 就是在 Python 中实例的__del__方法。使用引用计数时,Finalizer 工作地不错。当一个对象的引用计数降到 0 的时候,Finalizer 就在对象被释放前调用了。对程序员来说这是直接明了且容易理解的。

垃圾回收的时候,调用 finalizer 就成了一个麻烦的问题,尤其是面对循环引用的问题时。如果在循环引用中的两个对象都有 finalizer,该怎么做?先调用哪个?在调用第一个 finalizer 之后,这个对象无法被释放因为第二个 finalizer 还能取到它。

因为这个问题没有好的解决办法,被有 finalizer 的对象引用的循环是无法释放的。相反的,这些对象被加进一个全局的无法回收垃圾列表中。程序应该总是可以重新编写来避免这个问题。作为最后的手段,程序可以读取这个全局列表并以一种对于当前应用有意义的方式释放这些引用循环。

代价是什么?

就像有些人说的,天底下没有免费的午餐。然而,这种垃圾回收形式是相当廉价的。最大的代价之一是每各容器对象额外需要的三个字的内存空间。还有维护容器集合的开销。对当前版本的垃圾收集器来说,基于 pybench 这个开销大概是速度下降百分之四。

垃圾回收器目前记录对象的三代信息。通过调整参数,垃圾回收花费的时间可以想多小就多小。对一些应用来说,关掉自动垃圾回收并在运行时显式调用也许是有意义的。然而,以默认的垃圾回收参数运行 pybench,垃圾回收花费的时间看起来并不大。显而易见,大量分配容器对象的应用会引起更多的垃圾回收时间。

目前的补丁增加了一个新的配置项来激活垃圾回收器。有垃圾回收器的 Python 与标准 Python 是二进制兼容的。如果这个选项是关闭的,对 Python 解释器的工作就没有影响。

我该怎么使用它?

只要下载目前版本的 Python 就可以了。垃圾回收器已经包括在了 2.0 以后的版本中,并且默认是默认开启的。如果你在用 Python 1.5.2 版,这里有一个也许能工作的老版本的补丁。如果你用的是 Windows 平台,你可以下载一个用来替代的 python15.dll。

Boehm-Demers 保守垃圾回收

这个补丁增加了一些修改到 Python 1.5.2,以使用 Boehm-Demers 保守垃圾回收。但是你必须先打上这个补丁。依然是采用了引用计数。垃圾回收器只释放引用计数没有释放的内存(即循环引用)。这样应该性能最好。你需要:

$ cd Python-1.5.2
$ patch -p1 < ../gc-malloc-cleanup.diff
$ patch -p1 < ../gc-boehm.diff
$ autoconf
$ ./configure --with-gc

这个补丁假设你安装了 libgc.a,使得 -lgc 链接选项可用(/usr/local/lib 也应该可以)。如果你没有这个库,在编译以前下载安装。

目前,这个补丁只在 Linux 上测试过。在其 他Unix 机器上也许也会工作。在我的 Linux 机器上,GC 版本的 Python 通过了所有的回归测试。

总结

以上就是本文关于Python内存管理方式和垃圾回收算法解析的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:Python算法输出1-9数组形成的结果为100的所有运算式、Python数据结构与算法之列表(链表,linked list)简单实现、Python算法之求n个节点不同二叉树个数等,有什么问题可以随时留言,小编会及时回复大家的。感谢朋友们对本站的支持!

Python 相关文章推荐
python操作mysql数据库
Mar 05 Python
Python基于辗转相除法求解最大公约数的方法示例
Apr 04 Python
深入浅析Python中list的复制及深拷贝与浅拷贝
Sep 03 Python
python中下标和切片的使用方法解析
Aug 27 Python
python实现通过队列完成进程间的多任务功能示例
Oct 28 Python
Python Django view 两种return的实现方式
Mar 16 Python
Ubuntu中配置TensorFlow使用环境的方法
Apr 21 Python
python+requests接口自动化框架的实现
Aug 31 Python
python将YUV420P文件转PNG图片格式的两种方法
Jan 22 Python
用Python制作音乐海报
Jan 26 Python
Python中使用Selenium环境安装的方法步骤
Feb 22 Python
Python-typing: 类型标注与支持 Any类型详解
May 10 Python
Python实现的人工神经网络算法示例【基于反向传播算法】
Nov 11 #Python
python中使用正则表达式的后向搜索肯定模式(推荐)
Nov 11 #Python
python基础练习之几个简单的游戏
Nov 10 #Python
Python实现购物车功能的方法分析
Nov 10 #Python
Python实现的单向循环链表功能示例
Nov 10 #Python
Python3中的列表,元组,字典,字符串相关知识小结
Nov 10 #Python
浅谈Python处理PDF的方法
Nov 10 #Python
You might like
php强制用户转向www域名的方法
2015/06/19 PHP
Wordpress ThickBox 点击图片显示下一张图的修改方法
2010/12/11 Javascript
javascript学习(二)javascript常见问题总结
2013/01/02 Javascript
兼容主流浏览器的jQuery+CSS 实现遮罩层的简单代码
2014/10/14 Javascript
javascript实现动态标签云
2015/10/16 Javascript
一个字符串中出现次数最多的字符 统计这个次数【实现代码】
2016/04/29 Javascript
JS正则表达式修饰符global(/g)用法分析
2016/12/27 Javascript
Bootstrap按钮组简单实现代码
2017/03/06 Javascript
浅谈FastClick 填坑及源码解析
2018/03/02 Javascript
JS实现方形抽奖效果
2018/08/27 Javascript
vue实现分环境打包步骤(给不同的环境配置相对应的打包命令)
2019/06/04 Javascript
vue+ESLint 配置保存 自动格式化代码
2020/03/17 Javascript
JavaScript鼠标悬停事件用法解析
2020/05/15 Javascript
python3模拟百度登录并实现百度贴吧签到示例分享(百度贴吧自动签到)
2014/02/24 Python
Python3里的super()和__class__使用介绍
2015/04/23 Python
Python对列表排序的方法实例分析
2015/05/16 Python
Python使用lxml模块和Requests模块抓取HTML页面的教程
2016/05/16 Python
利用Python批量生成任意尺寸的图片
2016/08/29 Python
Django中使用Celery的方法示例
2018/11/29 Python
django 基于中间件实现限制ip频繁访问过程详解
2019/07/30 Python
python找出列表中大于某个阈值的数据段示例
2019/11/24 Python
手动安装python3.6的操作过程详解
2020/01/13 Python
对python pandas中 inplace 参数的理解
2020/06/27 Python
Python extract及contains方法代码实例
2020/09/11 Python
塑料制成的可水洗的编织平底鞋和鞋子:Rothy’s
2018/09/16 全球购物
Myprotein荷兰官网:欧洲第一运动营养品牌
2020/07/11 全球购物
数据库设计的包括哪两种,请分别进行说明
2016/07/15 面试题
编程用JAVA解析XML的方式
2013/07/07 面试题
母校寄语大全
2014/04/10 职场文书
数学系毕业生求职信
2014/05/29 职场文书
感恩老师演讲稿400字
2014/08/28 职场文书
个人整改方案范文
2014/10/25 职场文书
见习报告格式范文
2014/11/08 职场文书
十二生肖观后感
2015/06/12 职场文书
Python Matplotlib库实现画局部图
2021/11/17 Python
Python可视化神器pyecharts之绘制地理图表练习
2022/07/07 Python