Go 博客
Go GC:优先考虑低延迟和简洁性
背景
Go 正在构建一个不仅面向 2015 年,更是面向 2025 年及以后的垃圾回收器 (GC):一个能够支持当今 软件开发,并随未来十年新软件和硬件一起扩展的 GC。这样的未来不容许 全程序暂停(stop-the-world)的 GC 停顿,这曾是 Go 等安全语言更广泛应用的阻碍。
Go 1.5 作为这一未来的初次呈现,其 GC 延迟远低于我们一年前设定的 10 毫秒目标。我们在 Gophercon 的一次演讲中展示了一些令人印象深刻的数字。延迟的改进引起了广泛关注;Robin Verlangen 的博客文章 数十亿请求与 Go 1.5 相遇 通过端到端结果验证了我们的方向。我们 还特别喜欢 Alan Shreve 的生产服务器图表以及他“天呐,减少了 85%”的 评论。
如今,16 GB 内存只需 100 美元,CPU 具有多核心,每个核心有多个硬件线程。十年后,这些硬件看起来会很过时,但如今用 Go 构建的软件将需要扩展以满足不断增长的需求和下一个重大事物。考虑到硬件将提供提高吞吐量的能力,Go 的垃圾回收器被设计成倾向于低延迟,并且只通过一个旋钮进行调优。Go 1.5 是迈向这条道路的第一大步,这些初步步骤将永远影响 Go 及其最能支持的应用。这篇博客文章对我们在 Go 1.5 收集器方面所做的工作进行了高层次的概述。
细节阐述
为了创建一个面向未来十年的垃圾回收器,我们转向了几十年前的一种算法。Go 的新垃圾回收器是一个并发、三色、标记-清除 收集器,这一思想最早由 Dijkstra 于 1978 年提出。这与当今大多数“企业级” 垃圾回收器有所不同,我们认为它非常适合现代硬件的特性和现代软件的延迟要求。
在三色收集器中,每个对象要么是白色、灰色或黑色,我们将堆视为一个连接对象的图。在 GC 周期开始时,所有对象都是白色。GC 访问所有根对象,这些对象是应用程序直接可访问的,例如全局变量和栈上的对象,并将它们标记为灰色。然后,GC 选择一个灰色对象,将其标记为黑色,然后扫描它以查找指向其他对象的指针。当扫描发现一个指向白色对象的指针时,它将该对象变为灰色。这个过程重复进行,直到没有更多灰色对象。此时,白色对象被认为是不可达的,可以被重用。
这一切都与应用程序并发发生,应用程序被称为修改器(mutator),它在收集器运行时改变指针。因此,修改器必须维护一个不变式:没有黑色对象指向白色对象,以免垃圾回收器丢失对已访问堆部分中的对象的跟踪。维护这个不变式是写屏障(write barrier)的工作,这是一个由修改器在堆中的指针被修改时运行的小函数。Go 的写屏障将当前是白色的、现在可达的对象标记为灰色,确保垃圾回收器最终会扫描它以查找指针。
决定何时完成查找所有灰色对象的工作是很微妙的,并且如果我们想避免阻塞修改器,它可能会昂贵且复杂。为了保持简单,Go 1.5 尽可能多地并发工作,然后短暂地全程序暂停(stop-the-world)以 检查所有潜在的灰色对象源。找到这次最终全程序暂停所需的时间与本次 GC 执行的总工作量之间的最佳平衡点是 Go 1.6 的一项主要交付成果。
当然,魔鬼藏在细节中。我们何时开始一个 GC 周期?我们用什么指标来做出这个决定?GC 应该如何与 Go 调度器交互?我们如何暂停一个修改器线程足够长的时间来扫描其栈? 我们如何表示白色、灰色和黑色,以便高效地找到并扫描灰色对象?我们如何知道根对象在哪里?我们如何知道对象中的指针位于何处?我们如何最大程度地减少内存 碎片?我们如何处理缓存性能问题?堆应该有多大?等等等等,有些与内存分配有关,有些与查找可达对象有关,有些与调度有关,但许多都与性能有关。对这些领域中每一个方面的低层讨论都超出了这篇博客 文章的范围。
在更高层面,解决性能问题的一种方法是添加 GC 旋钮,每个性能问题对应一个旋钮。然后程序员可以转动这些旋钮,为他们的应用程序寻找合适的设置。缺点 是,经过十年,每年新增一两个 旋钮后,您最终会面临“GC 旋钮调整员 就业法案”。Go 不会走这条路。相反,我们提供一个单一的旋钮,称为 GOGC。这个值 控制着堆的总大小相对于可达对象大小的比例。默认值 100 意味着上次回收后,总堆大小现在比可达对象大小大 100%(即两倍)。200 意味着总堆大小比可达对象大小大 200%(即三倍)。如果您想减少花费在 GC 上的总时间,请 增加 GOGC。如果您想用更多的 GC 时间换取更少的内存,请 降低 GOGC。
更重要的是,随着下一代硬件的出现,内存翻倍,简单地将 GOGC 加倍将使 GC 周期数减少一半。另一方面,由于 GOGC 基于可达对象的大小,将可达对象翻倍来使负载翻倍无需重新调整。应用程序只需扩展即可。此外, 由于不受持续支持数十个旋钮的束缚, 运行时团队可以根据真实客户应用程序的反馈来专注于改进运行时。
结论
Go 1.5 的 GC 开启了一个未来,其中全程序暂停不再是转向安全语言的障碍。 在这个未来中,应用程序将随着硬件轻松扩展,并且随着硬件变得更加强大,GC 将不再是更好、 更具可伸缩性的软件的障碍。对于未来十年乃至更远,这是一个很好的位置。有关 1.5 GC 以及我们如何消除延迟问题的更多详细信息,请参阅 Go GC:延迟问题已解决(演示)或 幻灯片。
下一篇文章: Golang UK 2015
上一篇文章: Go 1.5 发布了
博客索引