土法炼钢兴趣小组的算法知识备份

【Garbage Collection Series】Go 语言垃圾回收 (GC) 深度解析

目录

引言

Go 语言的 GC 设计哲学与 Java 截然不同。Go 专注于低延迟 (Low Latency),牺牲了一部分吞吐量来换取极短的 STW (Stop The World) 时间。Go 1.5 引入了并发标记,Go 1.8 引入了混合写屏障,使得 STW 时间通常在微秒级别。

1. 三色标记法 (Tri-color Marking)

Go 使用并发的三色标记清除算法。

Tri-color Marking

1.1 核心概念

1.2 标记过程

  1. 初始状态: 所有对象均为白色。
  2. 根扫描: 将 GC Roots 直接引用的对象标记为灰色。
  3. 并发扫描:
  1. 完成: 此时只剩下黑色和白色对象。白色对象即为垃圾。

1.3 并发问题:漏标

在并发标记过程中,用户线程 (Mutator) 可能会修改引用关系,导致漏标(即把活跃对象误判为垃圾)。漏标发生必须同时满足两个条件: 1. 条件 1: 黑色对象引用了一个白色对象。 2. 条件 2: 灰色对象与该白色对象之间的引用关系被破坏(删除)。

为了防止漏标,必须破坏上述两个条件之一。这就是写屏障的作用。

2. 混合写屏障 (Hybrid Write Barrier)

Go 1.8 之前使用的是 Dijkstra 写屏障或 Yuasa 写屏障,Go 1.8 引入了混合写屏障,结合了两者的优点。

2.1 插入写屏障 (Dijkstra) - 破坏条件 1

2.2 删除写屏障 (Yuasa) - 破坏条件 2

2.3 混合写屏障 (Hybrid)

Go 结合了两者,极大地减少了 STW 时间(不再需要 STW 重新扫描栈)。

混合写屏障伪代码:

// slot 是指针在内存中的地址
// ptr 是要写入的新值
writePointer(slot, ptr):
  shade(*slot) // 1. 删除写屏障:将旧值置灰 (保护旧路径)
  shade(ptr)  // 2. 插入写屏障:将新值置灰 (保护新路径)
  *slot = ptr  // 3. 更新指针

3. GC Pacing (调优与触发)

Go GC 的触发不仅仅依赖于固定阈值,而是基于反馈控制算法,称为 GC Pacing

3.1 GOGC 参数

GOGC 环境变量控制了下一次 GC 的触发堆大小。默认值为 100。 \[ \text{NextGC} = \text{HeapMarked} + \text{HeapMarked} \times \frac{\text{GOGC}}{100} \]

3.2 辅助标记 (Mark Assist)

为了防止在并发标记期间,用户线程分配内存的速度超过 GC 标记的速度,Go 引入了辅助标记。 * 机制: 分配内存过快的 Goroutine 会被”征用”一部分 CPU 时间来协助进行标记工作。 * 效果: 这是一个背压 (Backpressure) 机制,限制了高分配速率的 Goroutine。

4. Green Tea GC (Go 1.25+ 实验性特性)

在 Go 1.25 中,Go 团队引入了一个代号为 Green Tea 的新 GC 实现(可通过 GOEXPERIMENT=greenteagc 开启)。这是对标记阶段的重大重构,旨在解决现代硬件上的”微架构灾难”。

4.1 背景:微架构灾难 (Microarchitectural Disaster)

传统的 GC 标记算法(Graph Flood / Mark-Sweep)在现代 CPU 上效率极低: * 随机内存访问: 追踪指针意味着在堆内存中随机跳转。 * Cache Miss: 这种随机性导致 CPU 缓存命中率极低。 * 流水线停顿: CPU 必须等待内存数据加载,导致大量指令周期被浪费(Stall)。 * NUMA 问题: 跨核心/跨插槽的内存访问延迟进一步加剧了性能损耗。

Go 团队发现,GC 标记阶段约 35% 的时间是纯粹的内存等待(Memory Stall)。

4.2 核心机制:面向页 (Page-Oriented)

Green Tea GC 的核心思想是:Work with pages, not objects. (处理页,而不是对象)。

Green Tea GC vs Graph Flood

4.2.1 数据结构变革

4.2.2 算法流程 (The Green Tea Algorithm)

Green Tea 利用”懒惰”(Laziness)来累积工作量,从而实现批量处理。

  1. 发现 (Discovery):
  1. 处理 (Processing):

这种机制使得 GC 能够在同一个页上一次性处理多个对象,极大地提高了空间局部性 (Spatial Locality)

4.3 向量化加速 (Vector Acceleration)

由于 Green Tea 将工作规整化为对页的处理,这使得利用 SIMD 指令集(如 AVX-512)成为可能。

4.3.1 扫描内核 (Scanning Kernel)

Green Tea 使用 AVX-512 指令集并行处理页的元数据。以下是硬件加速的逻辑步骤:

  1. 加载元数据: 将整个页的 seenscanned 位图加载到 512 位寄存器中。
  2. 计算活跃对象: \[ \text{ActiveObjects} = \text{Seen} \ \& \ (\sim \text{Scanned}) \]
  3. 位图膨胀 (Bit Expansion):
  1. 指针过滤:
  1. 批量提取:

4.3.2 性能收益

4.4 总结与对比

特性 传统 GC (Graph Flood) Green Tea GC
工作单元 对象 (Object) 页 (Page, 8KB)
遍历顺序 深度优先 (DFS) 广度优先 (BFS)
内存访问 随机跳跃 (Random) 线性扫描 (Linear)
硬件利用 差 (Cache Miss 高) 优 (Cache Friendly + SIMD)
CPU 开销 基准 降低 10% - 40%

Green Tea 标志着 GC 算法从纯软件算法向软硬协同优化的转变。它不再把 CPU 视为黑盒,而是针对现代处理器的缓存层级和向量指令进行了深度定制。

总结

Go GC 的核心在于低延迟。 1. 并发三色标记: 减少 STW。 2. 混合写屏障: 消除栈重扫的 STW,允许极短的停顿。 3. GC Pacing: 动态调整 GC 频率和辅助标记力度,平衡吞吐量和延迟。 4. Green Tea (新): 通过页级扫描和向量化指令,进一步压榨硬件性能。


如果你想了解 Go GC 与调度器如何协作(Mark Assist 抢占、STW 协调、P 的角色),可以读 Go 调度器深度拆解


By .