Irbisa · Cheatsheet
Runtime, Scheduler & GC
G-M-P, preemption, escape analysis, GOGC, GOMEMLIMIT, pprof
Who frees memory in Go, and can a Go program still leak?
How does the Go scheduler work: what are G, M and P, and why does
Pexist?With
GOMAXPROCS(1)this program hung forever on Go 1.13 and printsdonetoday. Is the Go scheduler cooperative or preemptive, and what changed?func main() { runtime.GOMAXPROCS(1) go func() { for { // no function calls, no allocations } }() time.Sleep(10 * time.Millisecond) fmt.Println("done")}What does
sysmondo in the Go runtime, and why does it run without aP?This recursion has no base case. What does the program print, how large did the goroutine's stack get, and why does
recovernot catch it?func depth(n int) int { return depth(n+1) + 1} func main() { defer func() { if r := recover(); r != nil { fmt.Println("recovered:", r) } }() fmt.Println(depth(0))}What does
GOMAXPROCScontrol, and how does a container CPU limit interact with it before and after Go 1.25?How do you find the function that burns CPU in a Go service with
pprof: enabling it, taking a profile, readingtopand the flame graph, and what does it cost?import ( "log" "net/http" _ "net/http/pprof" // registers /debug/pprof/* on http.DefaultServeMux)…What is escape analysis? Which of these functions put a value on the heap, and how do you check?
type Point struct{ X, Y int } var global *Point func local() int { p := &Point{1, 2} return p.X} func returned() *Point { p := Point{1, 2} return &p} func stored() { global = &Point{3, 4} }func printed(n int) { fmt.Println(n) }How does Go's garbage collector work: tri-colour marking, the write barrier, and when does the world stop?
What does
GOGCcontrol, why does a service with a 100 MB live heap use about 200 MB, and what happens when you raise it?What do
GODEBUG=gctrace=1andschedtrace=1000print, and how do you read agctraceline?$ GODEBUG=gctrace=1,schedtrace=1000 ./service 2> runtime.log gc 42 @12.513s 4%: 0.031+3.1+0.052 ms clock, 0.25+0.40/6.1/9.0+0.42 ms cpu, 118->121->62 MB, 124 MB goal, 0 MB stacks, 0 MB globals, 8 P SCHED 13004ms: gomaxprocs=8 idleprocs=0 threads=14 spinningthreads=0 needspinning=0 idlethreads=4 runqueue=37 [12 9 30 4 18 22 7 15]Find the bug: the service keeps only a 256-byte preview per upload, yet the live heap grows by megabytes with every file.
var mu sync.Mutexvar previews = map[string][]byte{} // first bytes, shown in the file list func saveUpload(id string, r io.Reader) error { data, err := io.ReadAll(r) // uploads are 1–20 MB if err != nil { return err } if err := store(id, data); err != nil { return err } mu.Lock() previews[id] = data[:min(len(data), 256)] mu.Unlock() return nil}What happens to the OS thread when a goroutine blocks on a channel, on a socket read, and on a file read or a
cgocall?// Regular-file reads and cgo calls each hold an OS thread while they run:// bound them, or one burst leaves you with thousands of threads.type diskPool struct{ sem chan struct{} } func newDiskPool(n int) *diskPool { return &diskPool{sem: make(chan struct{}, n)} }…How does the Go compiler decide what to inline, and why can inlining change whether a value escapes to the heap?
type Point struct{ X, Y int } // -m: can inline NewPointfunc NewPoint(x, y int) *Point { return &Point{X: x, Y: y} } //go:noinline…How does the Go memory allocator work (size classes,
mcache,mcentral,mheap), and how does it limit fragmentation without moving objects?A service spends a third of its CPU in
runtime.mallocgcand the GC workers. How do you cut allocations on the hot path, and how do you prove each fix?type Row struct { Name string Count int64} var bufs = sync.Pool{New: func() any { return new(bytes.Buffer) }}…Find the bug: under load, some API responses contain another request's JSON.
var bufPool = sync.Pool{New: func() any { return new(bytes.Buffer) }} func encode(v any) ([]byte, error) { buf := bufPool.Get().(*bytes.Buffer) buf.Reset() defer bufPool.Put(buf) if err := json.NewEncoder(buf).Encode(v); err != nil { return nil, err } return buf.Bytes(), nil}The heap profile is flat at 300 MB, yet the container's RSS stays at 1.4 GB and
runtime.GC()does not move it. Where is the memory?// metrics.Read does not stop the world; runtime.ReadMemStats does, briefly.var samples = []metrics.Sample{ {Name: "/memory/classes/total:bytes"}, // everything the runtime has mapped {Name: "/memory/classes/heap/released:bytes"}, // returned to the OS: not in RSS {Name: "/memory/classes/heap/objects:bytes"}, // live objects plus unswept garbage {Name: "/memory/classes/heap/free:bytes"}, // free pages the process still holds…How do you find a memory leak in a Go service, and when do you read
inuse_spacerather thanalloc_spacein a heap profile?# live heap now, by allocation sitego tool pprof -inuse_space 'http://127.0.0.1:6060/debug/pprof/heap?gc=1' # two snapshots ten minutes apart, then show only what grewcurl -so before.pb.gz 'http://127.0.0.1:6060/debug/pprof/heap?gc=1'sleep 600…A 2 GiB pod with a ~700 MB live heap is OOM-killed during traffic spikes, and an old runbook suggests a 1 GiB ballast. What do you set instead, and what does it count?
// Read the cgroup v2 limit and keep headroom for what GOMEMLIMIT does not count.func setMemoryLimit(fraction float64) { b, err := os.ReadFile("/sys/fs/cgroup/memory.max") // "max" when unlimited if err != nil { return }…Under load, throughput collapses and
gctraceshows back-to-back cycles with assist time larger than background marking. What is GC mark assist, and how do you fix it?// Is the collector being paid for by request goroutines?// Counters are cumulative since start: read twice and compare the deltas.// "dedicated" is the dedicated background marking workers.func gcShares() (assist, background float64) { s := []metrics.Sample{ {Name: "/cpu/classes/gc/mark/assist:cpu-seconds"},…An architect rejects Go for a 40 GB in-memory cache because "the GC has to walk 40 GB with the world stopped". What is actually stopped, and what does grow with the heap?
// wrong: ten million pointers the marker follows on every cycletype slowCache struct{ m map[string]*Entry } // right: no pointers inside, so the GC does not scan the data at alltype cache struct { index map[uint64]extent // hashed key -> position; no pointers in key or value…What changed in the garbage collector in Go 1.26 with Green Tea, and what should a team check when upgrading?
p99 is 800 ms but the CPU profile explains almost none of it. Which profiles show what requests were waiting for, and when do you need
go tool trace?var fr = trace.NewFlightRecorder(trace.FlightRecorderConfig{ MinAge: 10 * time.Second, // keep at least the last 10 s MaxBytes: 64 << 20,}) func main() {…What does a
cgocall cost the scheduler, and why must C never keep a Go pointer, let alone auintptr, after the call returns?/*#include <stddef.h>static void *kept;static void register_buffer(void *p, size_t n) { kept = p; }static void unregister_buffer(void) { kept = NULL; }*/…