Go语言的垃圾回收算法被吹过头?与Java比如何?

时间:2017-09-09 12:40

Go语言正在构建的垃圾收集器(GC),似乎并不像宣传中那样的,技术上迎来了巨大突破。那么,与Java语言作对比之后,该怎么选择呢?写在前面

最近,我读到一些大肆宣传Go语言最新垃圾回收器的文章,这些文章对垃圾回收器的描述让我感到有些厌烦。这些文章有些是来自Go项目。他们宣称GC技术正迎来巨大突破。

下面Go团队在2015年8月发布的新垃圾回收器的启动声明:https://blog.golang.org/go15gc

Go正在构建一个划时代垃圾回收器,2015年,甚至到2025年,或者更久……Go 1.5的GC把我们带入了一个新时代,垃圾回收停顿不再成为使用新语言的障碍。应用程序可以很容易地随着硬件进行伸缩,而且随着硬件越来越强大,GC不再是构建可伸缩软件的阻碍。一个新的时代正在开启。

Go团队不仅宣称他们已经解决了GC停顿问题,而且让整件事情变得更加“傻瓜”化:

从更高层面解决性能问题的方式之一是增加GC选项(也就是GC配置参数),每个性能问题使用一个选项。程序员可以通过选项为他们的应用程序找到合适的设置。不过,这种方式的不足之处在于,选项数量会不断增加,到最后很可能会需要一部“GC选项操作者就业草案”。Go不想继续走这条路。相反,我们只提供了一个选项,也就是GOGC。

而且,因为不需要支持太多的选项,运行时团队可以集中精力根据真实的用户反馈来改进运行时。

我相信很多Go语言用户对新的运行时还是很满意的。不过我对之前的那些观点有异议,对于我来说,它们是对市场的误导。这些观点在博客圈内重复出现,我想是时候对它们进行深入分析了。

事实上,Go的GC并没有真正实现任何新的想法或做出任何有价值的研究。他们在声明里也承认,他们的回收器是一种并发的标记并清除回收器,而这种想法在70年代就有了。他们的回收器之所以还值得一提,完全是因为它对停顿时间进行了改进,而这是以牺牲GC其它方面的特性为代价的。Go相关的技术讨论和发行材料并没有提到他们在这个问题上所做出的折衷,让那些不熟悉垃圾回收技术的开发人员不知道这些问题的存在,还暗示Go的其它竞争者制造的都是垃圾。而Go也在强化这种误导:

为了创建划时代的垃圾回收器,我们在很多年前就采用了一种算法。Go的新回收器是一种并发的、三基色的、标记并清除回收器,它的设计想法是由Dijkstra在1978年提出的。它有别于现今大多数“企业”级垃圾回收器,而且我们相信它跟现代硬件的属性和现代软件的低延迟需求非常匹配。

读完这段声明,你会感觉过去40年的“企业”级GC研究没有任何进展。

GC理论基础

下面列出了在设计垃圾回收算法时要考虑的一些因素:

程序吞吐量:回收算法会在多大程度上拖慢程序?有时候,这个是通过回收占用的CPU时间与其它CPU时间的百分比来描述的。

GC吞吐量:在给定的CPU时间内,回收器可以回收多少垃圾?

堆内存开销:回收器最少需要多少额外的内存开销?如果回收器在回收垃圾时需要分配临时的内存,对于程序的内存使用是否会有严重影响?

停顿时间:回收器会造成多长时间的停顿?

停顿频率:回收器造成的停顿频率是怎样的?

停顿分布:停顿有时候很短暂,有时候很长?还是选择长一点但保持一致的停顿时间?

分配性能:新内存的分配是快、慢还是无法预测?

压缩:当堆内存里还有小块碎片化的内存可用时,回收器是否仍然抛出内存不足(OOM)的错误?如果不是,那么你是否发现程序越来越慢,并最终死掉,尽管仍然还有足够的内存可用?

并发:回收器是如何利用多核机器的?

伸缩:当堆内存变大时,回收器该如何工作?

调优:回收器的默认使用或在进行调优时,它的配置有多复杂?

预热时间:回收算法是否会根据已发生的行为进行自我调节?如果是,需要多长时间?

页释放:回收算法会把未使用的内存释放回给操作系统吗?如果会,会在什么时候发生?

可移植性:回收器是否能够在提供了较弱内存一致性保证的CPU架构上运行?

兼容性:回收器可以跟哪些编程语言和编译器一起工作?它可以跟那些并非为GC设计的编程语言一起工作吗,比如C++?它要求对编译器作出改动吗?如果是,那么是否意味着改变GC算法就需要对程序和依赖项进行重新编译?

可见,在设计垃圾回收器时需要考虑很多不同的因素,而且它们中有些还会影响到平台生态系统的设计。我甚至都不敢确定以上给出的列表是否包含了所有因素。