您现在的位置: 主页 > 上位机技术 > JAVA > 为什么Python工程师很少像Java工程师那样讨论垃圾回收?
本文所属标签:
为本文创立个标签吧:

为什么Python工程师很少像Java工程师那样讨论垃圾回收?

来源:网络整理 网络用户发布,如有版权联系网管删除 2018-07-23 

呵呵这个问题挺有趣的。
“用的人少所以讨论也少”固然是因素之一,“写C++的人才讨论Java垃圾回收”的情况也不能说不存在…但我觉得还有若干别的因素更重要。

首先自然是:其实也有不少人讨论Python的内存管理或者说GC机制,不过题主之前未必留意了,自然就觉得没人讨论。当然,相对于Java开发者而言,讨论Python的内存管理/GC的开发者确实是少得多。下面说说让我感兴趣的几个因素。

1. 是否有选择,有怎样的选择?

Java的默认实现,Oracle/Sun JDK里的HotSpot VM,向用户提供了相当丰富的GC选项,既可以在很粗的粒度上配置(例如选择GC算法搭配),也可以在很细的粒度上配置(例如配置各收集器的具体启发条件(heuristics))。而且在该JDK的核心部分作为OpenJDK开源出来之后,大家都可以跳进去把各种本来不是暴露给用户用的参数拿来(乱)配置…
一方面,这让用户可以针对自己的应用情况做有针对性的GC配置,特别是可以让对GC有深入了解的人可以做非常精确的配置,这有利于Java应用与HotSpot VM的GC之间达到更好的配合。
然而另一方面,这么多选项容易让用户感到:(a) 困惑 (b) 厌烦 (c) 乱兴奋。

HotSpot VM的主要竞争对手们,IBM J9和Oracle JRockit则采用稍微不同的做法:它们同样提供丰富的配置选项,但通常只需要用户表达清楚“意图”而无需关心底下的细节,例如J9的 -Xgcpolicy:{optthruput,optavgpause,gencon,balanced,subpool},JRockit的 -XgcPrio:{throughput,pausetime,deterministic},只需要把Java应用的场景表达出来,JVM就会自动选择对应的各种细节配置,这就好多了。
而像Azul SystemsZing JVM则是更进一步,只提供一种GC实现C4 GC。而这个GC实现自身就非常有弹性,可以应对各种不同的使用场景,不需要用户做多少配置就能工作得很好,用户需要担心的事情就更少了。

我也见过一些有趣的JVM实现,可以在运行时自动根据情况切换GC算法,这样就完全不需要用户去配置啥了。但这样实现起来会很麻烦,而且实际效果未必有字面上看起来那么神奇,所以主流JVM都不这么做。

显然,HotSpot VM在这方面有待改善。它的核心开发组早就意识到了这点,已经有考虑在未来版本里提供更智能/方便的配置选项,类似J9 / JRockit的那种,以方便用户减少配置GC时需要关心的细节。不过目前它就是这样,被人吐槽也是应该的。

Python方面,其默认实现CPython提供了什么GC配置选项呢?
基本上什么也没提供。唯一能靠谱的配置的就是禁用 (cycle-) GC 。
既然无可配置,这不在程序员控制范围之内的事情还担心来干嘛呢?

PyPy倒是提供了一些GC配置选项,但它还远没成为主流Python选择,大家对它的正确配置方式关注的也就没那么多。

2. 是否知道问题有多严重?

主流JVM实现们都提供了打印GC日志的功能,而且可以配置日志的精细程序(例如 -verbose:gc vs. -XX:+PrintGCDetails );还有许多工具可以分析GC日志,将其可视化,有些还能提供挑优建议。这就让用户可以很方便的获取到足够的数据来发现、了解和解决问题。
当然,要如何正确理解这些信息也是门技术活。如果看到了一大堆数据但不够了解其背后的意义,那就又容易陷入上面所说的 (a) 困惑 (b) 厌烦 (c) 乱兴奋 的情况…

这“乱兴奋”是啥状况呢?Java应用其实可能遇到许多不同种类的性能问题,有些可能是Java代码自身没组织好,有些可能需要更好的JIT编译器优化,有些可能是GC问题。前两者也可能是很重要的问题,但JVM自身并没有提供好办法让大家去了解状况,而GC日志却是大家都能轻易获取的。
于是很多Java程序员或许过多的、不必要的关注GC问题了。有些Java应用对响应性的要求根本没到GC需要调优的程度,它的开发们却可能在忙着要想办法把GC暂停时间降下来了…

Python的默认实现CPython,只能配置打印cycle GC的收集状况,而无法打印引用计数带来的开销。
可能很多人会觉得引用计数有啥开销了,反正每次引用计数的更新开销都很小而且还均摊在整个程序的运行过程中,根本无需担心然而没有数据说个啥呢。

Hans Boehm在2003年专门发了篇论文提醒大家引用计数(朴素和延迟)都有值得关注的开销:The Space Cost of Lazy Reference Counting。有趣的是,其中有个例子展示了朴素引用计数(借助shared_ptr)比tracing GC的max pause time更高的情况。

有经验的C++程序员都会知道:

  • 当对象的所有权(ownership)不重要时,用裸指针;
  • 当对象的所有权可以唯一确定时,用unique_ptr。能用unique_ptr绝对不要用shared_ptr;
  • 要处理所有权复杂的情况时,可以用shared_ptr但不要滥用;当引用关系不影响所有权时,用weak_ptr。
C++程序员大都听说过不要滥用shared_ptr,然而CPython的现实就像是一个彻底滥用了shared_ptr的C++程序一样,连 PyIntObject / PyLongObject 也是引用计数的还有啥好说呢。

CPython采用的引用计数是最朴素的实现方式:局部变量、全局变量和对象字段都参与到引用计数中,而且引用计数的更新是在锁下同步的;外加朴素的mark-sweep备份来处理循环引用。
其实只要能让局部变量不参与到引用计数中,程序的吞吐量性能(throughput)就可以有不少提升这种做法叫做“延迟引用计数”(deferred reference counting,DRC)。这种做法最初在这篇论文提出:An Efficient, Incremental Garbage Collector,发表于1976年。近年来还有些进一步优化引用计数实现的办法,例如这两篇论文所总结/创新的:论文1论文2

CPython的实现中,GIL难以有效的去除的原因之一就是为了迁就引用计数。当一个Python线程在运行时,它会获取GIL以保证它对对象引用计数的更新是全局同步的。由于引用计数的实现细节被CPython的C API暴露到了外部的C扩展,要保持这些扩展完全兼容,就得维持或模拟CPython引用计数的实现,这就麻烦了…

大家听说过对CPython的GIL的抱怨不?经常听到对不对?
有多少一般Python用户知道吐槽GIL其实真的在吐槽的就是CPython的引用计数及C API实现?

说了半天,回归主题:CPython在帮Python程序自动管理内存时,引用计数到底带来了多少开销,大家心里有数么?反正CPython自身不提供相关任何日志,想弄清楚还挺麻烦的。
CPython那备份的mark-sweep GC也很朴素,一不小心也会带来问题…不知道大家关注过么?这个倒是可以打印日志的。

3. 程序规模或程序组织方式是否容易引起GC问题?

这个展开写起来有点麻烦…先偷个懒,抛出问题,回头有空再更新(逃

大家写Python脚本时,很多时候只是在比较少的数据量上跑很短时间,那引用计数或GC就算再怎么开销大其实也无所谓。
假如说CPython能改用DRC,免费提升大家的Python程序10%的吞吐量,这好不?想想,一个脚本本来要跑100秒,现在只要跑90秒了…好像也没啥差别。

在服务器端跑长时间运行的Python程序的就稍微惨一些。看这个案例:An arm wrestle with Python’s garbage collector Oyster.com Tech Blog

现在部署量最大的Java程序的场景,不是在服务器端就是在移动设备上。两者虽然看似是两个极端,但对响应性的需求却颇为相似。
在服务器端上,我之前在另一个问题的回答里提到,十来GB、几百GB的GC堆也不稀罕,这种规模上GC效率低就很有问题了。
在移动设备上,应用吃内存也是越来越多,而且对响应性的要求更是高。以前Java ME还流行的时候还比较多JVM参与,现在随着Android的普及,更多Java程序是跑在Dalvik / ART上。以ART为例,为了降低GC对应用响应性的影响,最近都用上并发拷贝(concurrent copying)了,动作相当激进。

4. 其它?

待补充

              查看评论 回复



嵌入式交流网主页 > 上位机技术 > JAVA > 为什么Python工程师很少像Java工程师那样讨论垃圾回收?
 配置 引用 计数

"为什么Python工程师很少像Java工程师那样讨论垃圾回收?"的相关文章

网站地图

围观()