文章主体是AI根据我的大纲生成的😁 ,其实大致意思是:

  • 程序运行过程中,对象会离散分布在不同内存区域
  • 在旧版三色标记法的清扫阶段,CPU会在这些内存跳转中产生很多开销
    • 对象在内存中离散分布,清扫时随机访问内存导致CPU一级/二级缓存频繁未命中,导致CPU空等
  • 新版GreenTea GC基于Memory Span,把 扫对象 变为了 扫8KB内存页,大幅减少缓存未命中
    • 基于此(CPU缓存友好的方案)还能利用现代CPU的AVX-512指令进行加速扫描。(对mspan内紧凑存储的mark bit(标记位向量)做批量位运算)
    • 还能通过mspan元数据批量过滤并跳过无活对象的mspan,大幅减少清扫工作量

注意,Go 的内存分配器(mcache/mcentral/mheap)本身就按页(mspan)管理内存,GreenTea GC只是复用了这个现有的机制

现有 GC

目前 Go 语言的 GC 可以概括为 并发三色标记清除(Concurrent Tri-color Mark and Sweep)

三色标记清除流程

Go 使用三种颜色来追踪对象的存活状态:

  • 白色(White):潜在的垃圾对象。尚未被 GC 访问到的对象。在 GC 开始时,所有对象都是白色的。遍历结束后,剩余的白色对象将被回收。
  • 灰色(Grey):活跃对象,但子对象尚未扫描。表示该对象已被访问,但其引用的子对象还未被全部访问完毕。
  • 黑色(Black):活跃对象,且子对象已扫描。表示该对象及其引用的所有子对象都已被访问完毕。

具体的运作流程如下:

  1. 初始状态:所有对象都被标记为白色。
  2. 根扫描:GC 从根对象(Root Set,如栈、全局变量等)开始,将所有直接引用的对象标记为灰色,并放入待处理队列。
  3. 循环扫描
    • 从灰色对象队列中取出一个对象。
    • 将其引用的所有子对象标记为灰色,放入队列。
    • 将当前对象标记为黑色。
  4. 完成标记:重复上述步骤,直到灰色对象队列为空。此时,内存中只剩下黑色和白色对象。
  5. 清除(Sweep):所有白色对象被认定为不可达(垃圾),将被回收。

在此过程中,为了保证并发安全性(即在 GC 运行时用户程序也能修改对象引用),Go 使用了 写屏障(Write Barrier)。写屏障的开启和关闭阶段构成了 STW(Stop-The-World)的主要耗时。

虽然这一机制非常成熟,但在某些场景下仍存在性能瓶颈。

主要问题:

  1. 无内存整理(No Compaction):Go 的 GC 比较“懒”,回收完垃圾后不会整理内存。这就好比书架上的书抽走几本后,空位留在那儿不动,久而久之,书架上到处都是小空隙(内存碎片)。
  2. 非分代(Non-Generational):所有对象不论“年龄”大小,都混在一起管理。
  3. 内存局部性差(关键痛点)
    • 简单来说,就是不同的小对象分散在内存的各个角落
    • 当 GC 开始干活时,它需要顺着引用关系一个个去找这些对象。如果对象 A 在东边,对象 B 在西边,GC 就得在内存里来回“奔波”。
    • 这种“东奔西跑”不仅累(占用 CPU),而且慢(等待内存响应)。CPU 很多时间都浪费在路上,而不是在干正事。

GreenTea GC 的设计理念

为了解决上述问题,Golang 团队(或社区提案)提出了 GreenTea GC。其核心思想是利用 内存跨度(Memory Span) 来强制实现内存局部性。

什么是 Memory Span?

Memory Span 是一个固定大小(例如 8KiB)的连续内存区域,专门用于分配小型对象(例如小于 512 字节)。

  • 对象聚集:当小对象逃逸到堆上时,它们会被分配到当前的 Memory Span 中。
  • 自动扩容:当一个 Memory Span 填满后,会分配一个新的 Span。
  • 适用场景:绝大多数结构体或树/图的节点都小于 512 字节,非常适合这种分配方式。

GreenTea GC 的运作机制

GreenTea GC 改变了 GC 的执行单位。它不再是以单个对象为粒度进行调度,而是以 Memory Span 为单位。

  1. 队列化处理:当 GC 触发时,所有的 Memory Span 会被加入到一个队列中。
  2. GMP 调度:利用 Go 现有的 GMP 模型,工作线程(Worker)从队列中获取 Memory Span 进行处理。这也支持工作窃取(Work Stealing)以平衡负载。
  3. 局部的三色标记清除:当一个线程处理某个 Memory Span 时,它只在该 Span 内部执行三色标记清除算法。

优势

GreenTea GC 通过改变内存布局和 GC 粒度,带来了显著的优势:

  • 减小跳转,少跑冤枉路:因为相关的小对象都挤在同一个 8KiB 的房间(Span)里,GC 扫地的时候,进一个房间就能把一堆对象都扫完,不用满世界乱跑了。
  • 最大限度利用 CPU 缓存:CPU 喜欢处理挨在一起的数据。数据凑得近,CPU 缓存命中率就高,处理速度直接起飞。
  • 内存分配时减少碎片:固定大小的房间(Span)分配给小对象,就像用标准集装箱装货,严丝合缝,大大减少了内存碎片。

总结

GreenTea GC 是对现有Go GC机制的一种补充和优化,特别是在处理大量小对象和复杂对象图时,通过强制内存局部性,有效地解决了“指针追逐”带来的性能损耗。 它让 Go 的运行时能更高效地利用现代 CPU 的缓存架构。