Write Barriers and Mark Assist
Write Barriers and Mark Assist
Overview
During concurrent mark, mutators keep running. Write barriers intercept pointer stores so the GC does not lose objects. When allocation outruns marking, mark assist makes allocating Gs help mark—coupling alloc rate to GC CPU on the request path.
Diagram: barrier keeps tri-color safe
mutator: x.f = p
│
v
write barrier (during concurrent mark)
│
v
shade / publish pointer to GC
alloc while GC behind ──► mark assist on same G
Without barriers, a black object could gain a white pointer the GC will never see → use-after-free class bugs.
When barriers are on
- Enabled around concurrent mark
- Cost shows up as extra work on pointer-heavy writes
- Reducing pointer density (flatten structs, fewer interfaces in hot graphs) helps
Mark assist
mutator allocates fast
→ GC behind on mark
→ allocator charges assist credit
→ G marks a bit before continuing
sequence (top → bottom):
actors: mutator G, allocator, mark work
mutator G --> allocator : mallocgc
allocator --> mutator G : assist debt
mutator G --> mark work : scan some greys
mutator G --> mutator G : continue user code
Symptom: latency spikes on alloc-heavy handlers correlating with GC cycles—not only STW.
Tuning angle
- Allocate less (best)
GOMEMLIMIT/GOGCshape frequency- Profile
runtime.gcAssistAlloc/ mark symbols - Avoid panicking into “just raise GOGC” without heap proof
Experiment
GODEBUG=gctrace=1 go run .package main
import ("fmt"; "runtime")
func main() {
var hold [][]byte
for i := 0; i < 2000; i++ {
hold = append(hold, make([]byte, 64*1024))
}
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Println("NumGC", m.NumGC, "GCCPUFraction", m.GCCPUFraction)
_ = hold
}What to notice: gctrace lines and rising NumGC under allocation bursts.
Try next: Compare assist-heavy run vs pre-sized slice with fewer growths.