GC Pacer¶
Garbage collection is not just about finding and freeing unused memory—it's about doing so at the right time, with the right pace. Go's GC Pacer is the brain behind this scheduling mechanism, ensuring that garbage collection completes before the heap grows too large, while minimizing CPU overhead and pause times.
This article explores the GC Pacer implementation in Go 1.23.12, primarily located in src/runtime/mgcpacer.go.
How GC Pacer Works¶
At its core, the GC Pacer answers a critical question: When should we start the next GC cycle, and how much work should mutators contribute to ensure we finish on time?
The Pacer operates through a feedback loop:
- Trigger Detection: When heap usage reaches the trigger point, background scanning begins
- Progress Monitoring: At each time slice (typically per allocation or per GC worker iteration), the Pacer evaluates:
- Current scanning progress (how much heap has been marked)
- Allocation rate (how fast the application is allocating)
- Dynamic Adjustment: Based on the ratio of scanning progress to allocation progress, the Pacer updates
assistRatio - CPU Utilization Control: The
assistRatiodetermines: - How much assist work each allocation must perform
- Ensures the GC cycle completes exactly when heap size reaches the target
This mechanism ensures that GC work is distributed throughout the allocation cycle, rather than causing long stop-the-world pauses.
Target Heap Calculation¶
The target heap size is primarily determined by the GOGC environment variable. The formula is straightforward:
However, the actual behavior includes important nuances:
- ±5% Tolerance: The Pacer allows the actual heap size to deviate by approximately ±5% from the target. This means:
- Slightly exceeding the target is acceptable (prioritizing application throughput)
- Falling slightly short is also acceptable (prioritizing memory efficiency)
- But significant overruns or underruns are penalized in subsequent cycles
GOMEMLIMIT Interaction¶
When GOMEMLIMIT is set, the behavior changes:
Critical constraint: GOMEMLIMIT must be set higher than the expected heap usage peak. If the application's live data exceeds this limit, the runtime has no choice but to panic with an out-of-memory error.
Key Components Yet to Explore¶
The GC Pacer coordinates with several other GC components. This article provides the high-level view, while future articles will dive deeper into:
- Scavenging: How the Pacer influences memory return to the OS
- 25% CPU Utilization Floor: Why the Pacer reserves at least 25% CPU for background GC workers
- Write Barrier: How the Pacer relies on write barriers to track pointer modifications
- GOMEMLIMIT Edge Cases: What happens when memory pressure exceeds limits
Summary¶
The GC Pacer is Go's solution to the fundamental challenge in concurrent garbage collection: balancing application throughput with memory reclamation. By continuously monitoring heap growth and allocation rate, and dynamically adjusting the burden placed on mutators, it achieves:
- Predictable pause times: GC work is evenly distributed
- Adaptive behavior: Responds to changes in allocation patterns
- Configurable targets: Via
GOGCandGOMEMLIMIT
Understanding the Pacer is crucial for tuning Go applications for performance, especially in memory-constrained environments or allocation-heavy workloads.