Go Green Tea Gc
介绍
Go Green Tea GC 改变不大,首先要了解一下 Go 以前是怎么处理垃圾回收的。
简单来说,传统 GC 的流程是:从 root 扫描开始,把发现的引用对象加入队列(标记为灰色),然后从队列中取出一个对象,继续扫描它的引用,如果引用对象还没被标记,就再次入队(标记为灰色),直到队列为空。最后把没有被标记的对象(白色)清除掉。
这个过程有一个根本性的问题——引用的对象散落在堆内存的各个角落,CPU 在处理队列中的对象时需要不断跳转访问不同内存地址,缓存命中率极低。事实上,超过 35% 的 CPU 周期都浪费在等待内存访问上,GC 效率因此大打折扣。
Green Tea 是怎么做的?
Green Tea 的做法是把扫描到引用的对象不加入队列,加入队列的是对象所在的 span,span 中维护两个 bitmap 来标记 span 中哪些对象的状态。
- Seen 位 :记录对象是否已被 GC 标记为可达。Seen = 可达标记位,防止同一个对象被重复入队。
- Scanned 位:记录对象的内部指针是否已经被遍历过。Scanned = 内部指针扫描状态,防止重复遍历对象内字段。
两个位的配合:
| Seen | Scanned | 状态描述 | 操作 |
|---|---|---|---|
| 0 | 0 | 新对象,没看过,没扫描 | 标记 Seen=1,入队扫描对象内指针 |
| 1 | 0 | 已知可达,但指针还没扫描 | 扫描指针,标记 Scanned=1 |
| 1 | 1 | 已可达且指针扫描过 | 跳过,不用再扫描 |
这样就能实现三色标记的“灰色→黑色”逻辑:
- 灰色:Seen=1, Scanned=0 → 可达但指针还没扫描
- 黑色:Seen=1, Scanned=1 → 可达且指针扫描完毕
- 白色:Seen=0 → 不可达或未标记
因为按 span 扫描上边的对象,所以 CPU 跳转更友好。
为什么会变快
- 减少队列操作频率 - 以 span 为粒度入队,远比以对象为粒度入队的频率低得多,在大量小对象的场景下尤为明显。入队/出队的调度开销因此大幅降低。
- 降低多核竞争 - 当 CPU 核心数越多时,原有方案中多个 goroutine 争抢队列的竞争越激烈。span 粒度的队列压力更小,调度器负载更轻,并发扩展性更好。
- 提升 CPU 缓存命中率 - span 内的对象在内存中是连续排列的,扫描时具有极强的空间局部性(Spatial Locality)。CPU 缓存行(Cache Line)可以一次加载多个相邻对象的数据,减少主存访问等待。传统 GC 在不同内存区域间随机跳跃,缓存几乎完全失效。
- 友好的 NUMA 架构适配 - 在 NUMA(非均匀内存访问)架构下,span 内的对象通常归属同一内存节点,访问延迟远低于跨节点访问。而传统的对象级跳跃访问极易触发跨节点内存读取,开销显著增大。
- 解锁硬件向量加速(SIMD)
AVX-512
Go 的 Green Tea GC 在扫描对象指针时,充分利用了 AVX-512 等现代 CPU 向量指令集的能力。传统 GC 往往是逐对象、逐字段地处理指针,这种逐步处理方式无法发挥 SIMD(单指令多数据)并行处理的优势。而在 Green Tea 中:
- Span 内连续对象 的指针信息被整理成紧凑的 bitmap 或数组。
- 扫描器可以一次性加载多个对象的指针信息到 SIMD 寄存器中。
- 同一条指令即可对多条指针进行标记检查、更新 Seen/Scanned 位,极大提高了指针遍历吞吐量。
这意味着在内存访问和 CPU 指令层面都能获得 批量处理 的加速效果,尤其在 span 中包含大量小对象的情况下性能提升明显。
并发与增量回收
Green Tea 仍然保持 Go GC 的 并发回收 和 增量标记 特性:
并发标记
- 多个 goroutine 扫描 span 内对象 bitmap,每个核心处理不同 span,减少全局队列竞争。
- Seen/Scanned 位保证即使多个 goroutine 扫描同一 span,也不会重复扫描对象内指针。
增量标记
- GC 标记阶段可以与应用程序 goroutine 并行执行,避免 STW(Stop-The-World)时间过长。
- 由于 span 内对象紧凑连续,增量标记的每次批量扫描都更高效,缓存命中率更稳定。
总结
Green Tea GC 的核心优化思路可以概括为:
- 队列粒度优化:对象级 → span 级,减少入队/出队开销。
- 位图标记:Seen/Scanned 双 bitmap,实现三色标记逻辑,避免重复扫描。
- 内存局部性优化:连续对象内存排列,提高 CPU 缓存命中率。
- 硬件加速:利用 AVX-512 等 SIMD 指令批量扫描对象指针。
- 并发友好:多核扩展性强,NUMA 架构适配良好。