Go 博客

Go 运行时:4 年后

Michael Knyszek
2022 年 9 月 26 日

自 2018 年我们发布上一篇关于 Go GC 的博客文章以来,Go GC 以及更广泛的 Go 运行时一直在稳步改进。我们解决了一些大型项目,这些项目源于真实的 Go 程序以及 Go 用户面临的实际挑战。让我们向您介绍一下亮点!

有什么新特性?

  • sync.Pool 是一个 GC 感知的内存复用工具,与之前相比,它对延迟的影响更小,并且更有效地回收内存。(Go 1.13)

  • Go 运行时更主动地将不需要的内存返回给操作系统,从而减少过多的内存消耗和内存不足错误的几率。这使空闲内存消耗降低高达 20%。(Go 1.13 和 1.14)

  • 在许多情况下,Go 运行时能够更轻松地抢占 goroutines,从而将 stop-the-world 延迟降低高达 90%。观看此处 Gophercon 2020 的演讲。 (Go 1.14)

  • 与之前相比,Go 运行时更有效地管理计时器,尤其是在具有许多 CPU 核心的机器上。(Go 1.14)

  • 在大多数情况下,使用 defer 语句延迟的函数调用现在与普通函数调用的开销一样小。观看此处 Gophercon 2020 的演讲。 (Go 1.14)

  • 内存分配器的慢路径与 CPU 核心扩展性更好,尤其是在高度并行的程序中,吞吐量增加高达 10%,尾部延迟减少高达 30%。(Go 1.14 和 1.15)

  • Go 内存统计信息现在可通过更精细、灵活和高效的 API 访问,即 runtime/metrics 包。这将获取运行时统计信息的延迟降低了两个数量级(毫秒级到微秒级)。(Go 1.16)

  • Go 调度器用于查找新工作的自旋 CPU 时间减少高达 30%。(Go 1.17)

  • Go 代码现在在 amd64、arm64 和 ppc64 上遵循基于寄存器的调用约定,将 CPU 效率提高高达 15%。(Go 1.17 和 Go 1.18)

  • Go GC 的内部记账和调度已重新设计,解决了与效率和鲁棒性相关的一系列长期存在的问题。对于 goroutines 栈占用内存比例较高的应用程序,这显著降低了应用程序的尾部延迟(高达 66%)。(Go 1.18)

  • Go GC 现在在应用程序空闲时限制自身的 CPU 使用率。这使得在非常空闲的应用程序中,GC 周期期间的 CPU 利用率降低了 75%,减少了可能干扰工作分配器的 CPU 峰值。(Go 1.19)

这些变化对用户来说大多是不可见的:他们熟悉和喜爱的 Go 代码只需升级 Go 就能运行得更好。

一个新的调节旋钮

Go 1.19 带来了一个长期以来备受期待的特性,它需要一些额外的操作才能使用,但潜力巨大:Go 运行时的软内存限制

多年来,Go GC 只有一个调优参数:GOGCGOGC 允许用户调整Go GC 在 CPU 开销和内存开销之间进行的权衡。多年来,这个“旋钮”很好地服务了 Go 社区,涵盖了各种各样的用例。

Go 运行时团队一直不愿在 Go 运行时中添加新的调节旋钮,这是有充分理由的:每个新的旋钮都代表了配置空间中一个新的维度,我们需要测试和维护它,这可能永远持续下去。旋钮的增多也会给 Go 开发者带来负担,使他们更难理解和有效使用这些旋钮。因此,Go 运行时一直倾向于在最小配置下表现合理。

那么为什么还要添加一个内存限制旋钮呢?

内存不像 CPU 时间那样易于替代。对于 CPU 时间,只要稍等片刻,将来总会有更多。但对于内存,您拥有的数量是有限的。

内存限制解决了两个问题。

首先,当应用程序的峰值内存使用量不可预测时,仅凭 GOGC 几乎无法防止内存耗尽。仅使用 GOGC 时,Go 运行时根本不知道它有多少可用内存。设置内存限制使运行时能够通过感知何时需要更努力地减少内存开销来应对瞬态、可恢复的负载峰值,从而提高鲁棒性。

其次,为了在不使用内存限制的情况下避免内存不足错误,必须根据峰值内存来调整 GOGC,即使应用程序未处于峰值内存使用状态且有大量可用内存,这也会导致更高的 GC CPU 开销以维持较低的内存开销。这在我们容器化的世界中尤为重要,其中程序被放置在具有特定和隔离内存预留的容器中;我们不妨利用这些预留!通过提供针对负载峰值的保护,设置内存限制可以使 GOGC 在 CPU 开销方面进行更激进的调优。

内存限制旨在易于采用且鲁棒。例如,它限制的是应用程序中 Go 部分的整体内存占用,而不仅仅是 Go 堆,因此用户无需担心计算 Go 运行时开销。运行时还会根据内存限制调整其内存回收策略,以便在面临内存压力时更主动地将内存返回给操作系统。

但是,尽管内存限制是一个强大的工具,但仍需谨慎使用。一个重要的注意事项是,它可能导致程序出现 GC 抖动(GC thrashing):即程序花费过多时间运行 GC,导致没有足够的时间进行有意义的进度。例如,如果内存限制设置得低于程序实际需要的内存量,则 Go 程序可能会发生抖动。GC 抖动在之前不太可能发生,除非明确将 GOGC 调整得非常偏向于内存使用。我们选择宁愿内存耗尽也不愿出现抖动,因此作为缓解措施,运行时会将 GC 限制在总 CPU 时间的 50%以内,即使这意味着超出内存限制。

所有这些都需要仔细考虑,因此作为这项工作的一部分,我们发布了一份全新的 GC 指南,其中包含交互式可视化,帮助您了解 GC 开销以及如何对其进行操作。

结论

试试内存限制!在生产环境中使用它!阅读GC 指南

我们一直在寻求关于如何改进 Go 的反馈,但听到它对您有效也非常有帮助。向我们发送反馈

下一篇文章:Go 十三年
上一篇文章:Go 开发者调查 2022 年第二季度结果
博客索引