What is a write barrier and which one does Go use?
A write barrier is a small piece of code the compiler inserts around pointer writes into heap memory. It runs only while the GC is marking; otherwise it is skipped by a cheap check along the lines of if writeBarrier.enabled. It tells the collector about pointer changes so the tri-color invariant still holds.
Since Go 1.8 the runtime uses a hybrid write barrier that combines:
- Yuasa deletion barrier: shade the old pointer being overwritten.
- Dijkstra insertion barrier: shade the new pointer being written.
// conceptual pseudo-code of *slot = ptr during marking
func writePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {
shade(*slot) // deletion (Yuasa)
shade(ptr) // insertion (Dijkstra)
*slot = ptr
}
Why the hybrid matters: stack writes have no barrier, because putting one on every local assignment would be far too expensive. With a Dijkstra-only barrier, Go 1.5 through 1.7 had to re-scan all stacks during a stop-the-world mark termination, which caused pauses of tens of milliseconds. The hybrid barrier lets each stack be scanned once and blackened, and it removed that re-scan. That change is why pauses dropped below a millisecond.
Gotcha: the barrier also makes pointer-heavy code slower during GC cycles. That is one more reason to prefer pointer-free data such as []int or struct values over []*T in hot structures.
More on Memory, GC & Runtime Internals
- Q335Describe Go's memory allocator: size classes, mcache/mcentral/mheap, and the tiny allocator.
- Q336Explain Go's concurrent tri-color mark-and-sweep garbage collector.
- Q338Walk through the phases of a GC cycle. Where are the stop-the-world pauses?
- Q339Is Go's GC generational or compacting? What is the "Green Tea" GC?
- Q340What does GOGC control, exactly? What happens with GOGC=off, 50 or 200?
- Q341What is GOMEMLIMIT and how would you set it in a container?