为什么 Go 不实现分代和紧凑 gc
有人在论坛里面问: 为什么 Golang 垃圾回收器不实现分代和紧凑 gc ?
Ian Lance Taylor 的回复:
这已经在过去讨论过了.
忽略细节, 紧凑(compacting) GC 的基本优点是:
- 避免碎片, 以及;
- 允许使用简单而有效的凹凸分配器(bump allocator).
但是, 现代的内存分配算法, 象 Go 运行时使用的基于 tcmalloc 的方案基本上没有碎片问题. 而凹凸分配器对于 Go 这样需要锁的多线程程序中的单线程程序是简单有效的. 一般来说, 这可能更多有效地使用一组每个线程缓存来分配内存, 而在这一点上你已经失去了凹凸分配器的优势. 所以我会断言, 一般来说有很多注意事项导致今天没有真正的优势为一个多线程程序使用压缩内存分配器. 我不是说使用压缩分配器有什么问题, 我只是认为它相对于非压缩的来说没有带来任何大的优势.
现在我们来考虑一下分代 GC. 分代 GC 的关键依赖于世代的假设: 分配在一个程序中的大部分值很快变得不会用到, 所以分代 GC 有一个优势就是可以花更多的时间查看最近分配的对象. 这里 Go 不同于许多垃圾收集语言, 因为许多对象是直接在程序栈(stack)上分配的. Go 编译器使用逃逸分析(escape analysis)来查找那些在编译时生命周期就已知的对象, 将它们分配到堆栈而不是垃圾收集的内存中. 所以一般来说, 在 Go 中, 与其他语言相比, 有很大比例的分代 GC 要找的很快不会用到的(quickly-unused)值不会分配在 GC 内存的首要位置. 所以分代 GC 能给 Go 带来的优势相对于其他语言要小.
更微妙的是, 大多数世代 GC 实现的隐藏点是减少垃圾收集带来的程序暂停的时间. 暂停期间只看最年轻的一代, 暂停时间很短. 然而, Go 使用了一个并发垃圾收集器, 并且在 Go 中程序暂停时间与年轻代或者任意代的大小无关. Go 基本上假设, 在多线程程序中, 通过在不同的核上并行运行 GC, 不是为了最小化 GC 时间去暂停导致程序运行更长的时间, 而是总体上花更多的总 CPU 时间在 GC 上.
总之, 分代 GC 可能仍然可以为 Go 带来显著的价值, 即减少并行 GC 时的工作量. 这是一个需要测试的假设. Go 当前的 GC 工作实际上正在密切关注一个相关但不同的假设: Go 程序可能倾向于按请求分配内存. 这里有一个描述. 这项工作正在进行中, 现实情况是否有利还有待观察.