
观察Go新垃圾回收器在堆中的运作Go 1.26版本将Green Tea设置为默认的垃圾回收器GC。去年发布的Go 1.25版本引入了这款新的垃圾回收器而在几个月前发布的Go 1.26中Green Tea成为了默认的回收器。我们通过 perf 工具来观察它的缓存友好性可视化堆内存以了解Go语言的内存分配方式还会探讨像Go这种非移动回收器在处理稀疏页面时面临的难题。内存分配情况对比Go语言通过在一个或多个8KiB页面组成的连续块在Go术语中称为 _span_内分配相同大小类别的对象来管理内存。我们随机分配三种不同大小小、中、大的对象然后检查它们在堆中的地址。运行程序后发现尽管是随机分配不同大小的对象但Go运行时会将相同大小的对象相邻放置而且即使运行了垃圾回收器对象的位置也不会发生移动。而C#中相同大小的对象并没有被分组放置在一起之后在不同的工作负载下C#会在内存中移动对象。标记 - 清除算法垃圾回收器从特定的根对象开始先进行 _标记_ 阶段沿着每个指针进行遍历直到访问完所有可访问的对象然后进行 _清除_ 阶段释放所有未被访问过的已分配对象。在Go语言中不同大小的对象会被分配到内存的不同区域垃圾回收器沿着指针进行遍历时会引入随机内存访问降低缓存友好性。在Green Tea中Go现在会扫描内存块以查找对象和指针并根据找到的指针将后续要扫描的内存块加入队列。使用 perf 工具观察到缓存未命中率每千条指令减少并且程序的整体运行速度加快。通过不同的代码和脚本进行测试发现新的垃圾回收器在每种工作负载下都有明显的性能提升但新的垃圾回收器的缓存未命中率似乎有所增加。进一步计算每千条指令的缓存未命中数MPKI后仍然没有看到预期的结果。最后在裸机x86/amd64 Linux机器上测试终于看到预期结果虽然L1缓存未命中率百分比保持不变或有所下降但 _L1 MPKI_ 明显下降。最坏情况下的清理由于Go语言不会移动内存来进行内存压缩或碎片整理释放了大部分对象但Go语言无法完全回收未使用的内存。通过程序测试在分配完对象后释放90%的对象原本希望内存使用量能减少90%但实际上并不会因为Go语言不会对对象进行压缩或移动。