Why Go’s GC Skips Scanning Pointer‑Free Objects and How It Boosts Performance
This article explains the Go runtime’s garbage‑collector optimization that skips scanning objects without pointers, describes how the noscan flag is set during memory allocation, shows the code paths that enforce the skip, benchmarks the performance gain, and offers practical tips for applying the technique in real‑world Go programs.
What Skipping Scanning Means
During the mark phase the GC checks an object’s sub‑objects (elements of arrays/slices, map keys and values, struct fields). Skipping scanning means the GC only checks the object itself and does not descend into its children.
Which Objects Can Be Skipped
If a map’s keys and values contain no pointers, the GC can treat the whole map as a single root and avoid scanning its internal storage. The same applies to slices, arrays, and structs whose fields contain no pointers.
How Skipping Is Implemented
The GC starts with root objects, processes them via
scanblockor
markrootSpans, and then calls
greyobject.
greyobjectchecks
s.spanclass.noscan(); if the span is marked noscan the function returns without enqueuing the object for further scanning.
How the noscan Flag Is Set
When memory is allocated (e.g., via
mallocgc), the runtime determines whether the type contains pointers. If the type has no pointers,
noscanis set by creating a
spanClasswith
makeSpanClass(sizeclass, true). This flag propagates to slices (via
makeslice), maps (via
makeBucketArray), and other allocations.
Performance Impact
Benchmarks comparing a slice of
int64(noscan) with a slice of
*int64(scanned) show a ~21% reduction in GC pause time while memory usage remains the same, demonstrating that skipping scanning can significantly speed up GC for pointer‑free data.
Practical Usage
To benefit from this optimization, prefer pointer‑free types for large collections. For caches that need mutable values, store values in a slice and map keys to slice indices, eliminating pointers in the map itself. This reduces GC work at the cost of extra memory and slightly more complex insertion/deletion logic.
Conclusion
The Go compiler marks memory allocated for pointer‑free types with the noscan flag, allowing the GC to skip deep scans of those objects. While this behavior is specific to the official Go compiler, minimizing pointer usage is a universal technique for reducing GC overhead.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.