A 40ms Go garbage collector pause caused by swap
A case of 40 ms stop-the-world pauses in Go, traced to the garbage collector touching metadata that had been swapped out, prompts broader scrutiny of how GC interacts with operating-system memory pressure. Commenters debate whether low-latency systems should avoid GC languages altogether, disable swap, or use techniques like `mlock`, cooperative or swap-aware collectors, and alternative algorithms such as on-the-fly GC or reference counting. The exchange highlights a trade-off between throughput and latency, and suggests that both runtime design and OS-level APIs significantly shape real-world pause behavior.
Go’s GC Design and “On-the-fly” Alternatives
- Go already uses a concurrent mark-sweep GC with very short typical pause times.
- Some ask why Go doesn’t adopt classic “on-the-fly” GC algorithms (e.g., DLG-style) that aim for zero stop-the-world (STW) pauses.
- Others note Go’s team likely knows these algorithms; tradeoffs in latency vs throughput and stack/coroutine scanning complexity may explain current choices.
- Fil-C’s alternative on-the-fly algorithm is mentioned as potentially relevant to Go’s coroutine model.
Swap, Page Faults, and Latency Spikes
- Core issue: when GC metadata or heap pages are swapped out, STW sections can accumulate many page faults, turning memory pressure into large latency spikes.
- Some argue that if you care about latency you should disable swap; others counter this only shifts stalls to code pages and may trigger OOM instead.
- Explanations given of how the kernel evicts anonymous vs file-backed pages; disabling swap doesn’t stop paging, just changes what gets evicted.
- Suggested mitigations: mlock/mlockall for critical regions or metadata, careful use of cgroups and swappiness, or accepting OOM-kill as preferable to swap storms.
Reference Counting vs Tracing GC
- Several posters claim reference counting (RC) offers more predictable performance; others dispute this, citing:
- Cascaded deallocations can be arbitrarily long.
- High overhead, especially with atomic RC in multithreaded programs.
- Problems with cycles and NUMA/locking.
- Some prefer hybrid or deferred RC schemes; others emphasize that industry has decades of experience showing pure RC has significant tradeoffs.
OS–GC Cooperation and Swap-Aware Designs
- Ideas raised:
- Swap-aware GC that prefetches or locks pages used in STW phases.
- Using existing APIs like mlock, madvise, mincore, or page-touching to manage residency.
- Deeper OS–GC cooperation (e.g., bookmark collectors, cooperative memory management, region-based GCs coordinating with madvise).
- Some see this as a missed research/practical opportunity; others doubt such deep integration will appear broadly soon.
Language Choice and Latency Requirements
- Several comments treat this as another data point: use Go where occasional GC latency is acceptable; choose Rust/C++ (RAII/manual control) for strict low-latency needs.
- The Discord case is referenced: even with little garbage, a large object graph plus periodic GC can cause unacceptable tail latency, motivating a rewrite in a non-GC language.