Golang实时GC的理论与实践总结
本文是总结Go语言的低延迟垃圾回收机制GC突出之处。
Pusher是一个简单的托管API,通过WebSockets集成到网络和移动应用程序或任何其他互联网连接的设备上,实现快速,轻松,安全地将实时双向功能。每天,Pusher实时发送数十亿条消息:从源发送信息到目标只需要100ms。我们如何实现这一目标? 一个关键因素是Go的低延迟垃圾回收器。
垃圾收集器是实时系统的缓慢的元凶,因为他们会暂停程序运行。所以在设计我们的新消息总线时,我们仔细选择了语言。 Go注重低延迟 ,但我们很谨慎:Go真正实现这一目标? 如果是,如何?
在这篇博文中,我们将看看Go的垃圾收集器。 我们将看看它是如何工作的(三色算法),为什么它这么杰出的工作(实现这样短的GC暂停),最重要的是,它是否真的这样工作(对GC暂停进行基准测试,并与其他语言进行比较)。
从Haskell到Go
我们一直在构建的系统是一个将已发布消息存储内存的pub / sub消息总线。Go这个版本是我们在用Haskell完成后第一个实现的重写。在发现GHC的垃圾收集器底层延迟问题后,我们在5月停止了对Haskell版本的工作。
GHC的暂停时间与工作集的大小(即内存中的对象数量)成正比。在我们的例子中,我们在内存中有很多对象,这导致了几百毫秒的暂停时间。 这是一个问题,任何GC当它完成收集时会阻塞程序。
进入Go以后,它不像GHC的停止世界收集器,Go的收集器与程序同时运行,这使得避免更长的停顿成为可能。 我们被Go注重低延迟特性鼓励,并发现它很有前途,特别是当我们读到其每一个新版本都有关改善延迟时。
并发垃圾收集器如何工作?
Go的GC如何实现这种并发? 其核心是三色标记-清除(tricolor mark-and-sweep)算法 。下面是一个动画(见原文动画),显示了算法的工作原理。 注意它让GC与程序同时运行;
这意味着暂停时间成为调度问题。 调度程序可以配置为仅在短时间内运行GC集合,与程序交织。 这对我们的低延迟要求是个好消息!
上面的动画详细显示了标记阶段。 GC仍然有两个停止世界阶段:对根对象的初始堆栈扫描,以及标记阶段的终止。 令人兴奋的是,这种终止阶段,最近被淘汰 。 我们将在后面讨论这个优化。 在实践中,我们发现对非常大的堆这些阶段的暂停时间为<1ms,。
使用并发GC,也有可能在多个处理器上并行运行GC。
延迟与吞吐量
如果并发GC针对很大的堆产生很低的延迟,为什么要使用stop-the-world收集器?是不是Go的并发垃圾收集器比GHC的stop-the-world的收集器更好 ?
不一定。 低延迟是要付出成本的。 最重要的成本降低吞吐量 。并发性需要额外的工作来同步和复制,这使得程序可以做有用的工作。GHC的垃圾收集器针对吞吐量进行了优化,但Go针对了延迟优化。 在Pusher,我们关心延迟,所以对于我们这是一个很好的方案。
并发垃圾回收的第二个成本是不可预测的堆增长。程序可以在GC运行时分配任意数量的内存。这意味着GC必须在堆达到目标最大之前运行。 但是如果GC运行得太快,那么将执行更多的收集。 这个代价是非常棘手。在Pusher,这种不可预测性不是一个问题;我们的程序倾向于以可预测的恒定速率分配存储器。
它在实践中如何执行?
到目前为止,Go的GC看起来很适合我们的延迟要求。 但它在实践中如何执行?
今年早些时候,当调查Haskell实现中的暂停时间时,我们为测量暂停创建了一个基准测试。基准程序重复地将消息推送到大小受限的缓冲区中。 旧消息不断地过期并变成垃圾。堆大小保持很大,这很重要,因为必须遍历堆才能检测哪些对象仍被引用。这就是为什么GC运行时间与它们之间的活对象/指针的数量成比例。
这里是Go中的基准,其中缓冲区被建模为数组:
|
结合其它人关于Ocaml Racket 和Java的基准测试,下面是在我系统上的测试结果,右边是最长暂停时间ms:
|
很惊讶Java两个测试表现很差,而OCaml表现非常好,不到〜3毫秒暂停时间,是由于为老一代使用增量的GC算法。 (我们不选择OCaml的主要原因是它支持多核并行性不好)。
如你所见,Go执行顺利,暂停时间约为7ms。 这符合我们的要求。
..
结论
这项GC调查的关键是针对更低的延迟或更高的吞吐量进行优化。他们也可能执行更好或更差,这取决于您的程序堆使用率。 (有很多的对象吗?他们有长或短的生命吗?)
重要的是要了解底层的GC算法,以决定它是否适合您的用例。 在实践中测试GC实现也很重要。您的基准测试应该与您打算实现的程序具有相同的堆使用率。 这将在实践中检查GC实施的有效性。正如我们所看到的,Go的实现不是没有故障,但在我们的情况下,问题是可以接受的。 我想在更多的语言中看到相同的基准,如果你想贡献:)
尽管有一些问题,Go的GC相比其他GCed语言执行得很好。Go团队一直在改进延迟,并继续这样做。 我们对Go的GC,理论和实践感到满意。