Watching Go's new garbage collector move through the heap
This article offers a deep technical dive into Go's Green Tea garbage collector, introduced in Go 1.25/1.26, showcasing its performance enhancements, especially regarding cache efficiency. Through practical code examples and perf profiling, it meticulously compares Go's non-moving GC behavior to C#'s, revealing significant speedups. Crucially, it highlights Go's inherent challenge with memory fragmentation that stems from its non-compacting design, a topic often debated by performance-conscious developers on HN.
The Lowdown
The article meticulously explores Go's new Green Tea garbage collector, now the default in Go 1.26, and its implications for memory management and performance. Using detailed code examples in Go and C#, it provides a visual and analytical comparison of how each language handles object allocation and garbage collection, particularly focusing on memory layout and cache efficiency.
- Go's Allocation Strategy: Go's allocator groups objects of similar sizes into contiguous 8KiB 'spans,' a design principle shared with allocators like tcmalloc. The article demonstrates this visually, showing how Go places same-sized objects adjacent to each other.
- Non-Moving GC: A key characteristic highlighted is Go's non-moving garbage collector; objects retain their memory addresses even after GC runs, a fact visually confirmed through heap dumps before and after collection.
- Green Tea's Improvement: The new Green Tea GC optimizes the 'mark' phase by intelligently scanning memory spans for objects and queuing future spans, rather than indiscriminately following every pointer. This change targets improved cache friendliness.
- Performance Benchmarking with
perf: The article employsperfto benchmark the old and new Go GCs across 'packed' and 'scattered' data structures, measuringcache-referencesandcache-misses. - L1 Cache Efficiency: Initial
perfresults for L3 cache were misleading. By switching to bare metal and analyzing L1-dcache events and Misses Per Kilo Instructions (MPKI), the author concretely demonstrates Green Tea's significant reduction in L1 cache misses and overall faster execution times, validating its cache-friendly design. - Go's Fragmentation Vulnerability: Despite Green Tea's advancements, Go's non-moving GC inherently leads to memory fragmentation. An experiment shows that freeing 90% of objects in Go fails to significantly reduce
HeapInusebecause the remaining objects cannot be moved to compact the heap. - C# as a Contrast: In contrast, a similar experiment in C# (which uses a compacting GC) successfully reclaims and consolidates freed memory, showcasing its ability to mitigate fragmentation.
- Manual Compaction (Go): The article illustrates that, in specific scenarios, developers can manually 'compact' Go's heap by copying live objects to new, contiguous memory regions, albeit this is not an automated GC behavior.
The article effectively demonstrates Green Tea's tangible performance benefits, particularly in optimizing cache utilization. However, it equally emphasizes that Go's fundamental choice of a non-moving garbage collector continues to leave it susceptible to memory fragmentation, a trade-off that distinguishes it from languages with compacting GCs like C#.