Performance and Diagnostics
Finding out why a Go service is slow, fat or stuck: the profiles and traces the runtime produces, the production symptoms they explain, and the GOGC, GOMEMLIMIT and GOMAXPROCS knobs you then set.
part ofGo (Golang)overview, primer and where to startread it →on this pageshowhide
explore
- Profiles and pprof25 questions
- CPU Profiles4 questions
- Heap and Allocation Profiles4 questions
- Goroutine Labels and Attribution4 questions
- net/http/pprof Endpoints4 questions
- Reading Profiles with pprof4 questions
- Block and Mutex Profiles5 questions
- What the Tracer Shows12 questions
- Reading an Execution Trace4 questions
- Scheduler Delay and Pauses4 questions
- Tasks, Regions and Events4 questions
- Symptoms in Production22 questions
- Finding a Goroutine Leak4 questions
- Runaway OS Threads5 questions
- Confirming a Deadlock4 questions
- Heap Growth Versus RSS5 questions
- File Descriptor Exhaustion4 questions
- Debugging a Live Process16 questions
- Breakpoints and Stepping4 questions
- Full Dumps and GOTRACEBACK4 questions
- Runtime Tracers via GODEBUG4 questions
- Capturing Stacks in Code4 questions
- Tuning Under Container Limits15 questions
- GOGC and Heap Growth5 questions
- GOMEMLIMIT and OOM Kills5 questions
- GOMAXPROCS Under CPU Quotas5 questions
- Optimising with Evidence12 questions
- Comparing Benchmark Runs4 questions
- Profile-Guided Optimization4 questions
- Inlining and Escape Hints4 questions
questions
102 · 6 sectionsHow do you capture a CPU profile from a running Go program with runtime/pprof?
basics
~10 sOpen a file, call runtime/pprof.StartCPUProfile(f), run the workload, then call StopCPUProfile before exiting. Stop flushes the buffered samples; without it the file is unusable. Inside tests, go test -cpuprofile does the same thing.
What does a blank import of net/http/pprof add to a Go program, and how do you reach it?
basics
~20 sA blank import of net/http/pprof runs the package's init, which registers /debug/pprof handlers on http.DefaultServeMux. It starts no listener of its own, so the routes are reachable only if some server actually serves that mux.
In `go tool pprof`, what does the `top` command list, and what do its flat and cum columns mean?
basics
~20 stop ranks functions by their share of a profile's samples. flat counts samples taken inside that function's own code; cum counts those plus everything it called. A wrapper has a tiny flat and a huge cum.
How does Go's mutex profile differ from its block profile in what each records?
basics
~20 sGo's block profile records the waiting goroutine's stack for any synchronisation wait — channels, select, mutex acquisition, WaitGroup. The mutex profile records contention on sync.Mutex and sync.RWMutex from the holder's side, attributing the waiters' delay to the unlock that released the lock.
In a Go heap profile, what is the difference between inuse_space and alloc_space?
basics
~20 sinuse_space is memory still live at the moment of the snapshot; alloc_space is every byte allocated since the process started, freed or not. One profile file carries both, and go tool pprof -sample_index chooses which you look at.
In `go tool trace`'s goroutine analysis, what does a goroutine's scheduler wait time mean?
basics
~20 sScheduler wait is time the goroutine was runnable but not running: it had work to do and sat queued, waiting for one of the GOMAXPROCS logical processors to pick it up. It is queueing delay, not blocking.
In `go tool trace`, what does a goroutine's GC mark assist time mean, and which goroutines pay it?
basics
~20 sMark assist is garbage-collection marking work done by an application goroutine itself, charged in proportion to how much it allocated during a collection cycle. Allocation-heavy goroutines pay it, and it lands directly in their latency.
What does the timeline view in go tool trace show, and which other views does it serve?
basics
~20 sThe timeline in go tool trace plots wall-clock time horizontally, one row per P (Go's scheduler slot), showing which goroutine ran where, plus GC and heap rows. The same page serves goroutine analysis, blocking profiles, scheduler latency, and minimum mutator utilization.
What does Go's execution tracer actually record, and why keep captures to a few seconds?
basics
~20 sGo's execution tracer records every traced runtime event with a timestamp — goroutine lifecycle, block reasons, syscalls, scheduler and GC phases — rather than sampling. That exhaustiveness makes files grow with activity, so captures stay short.
In Go's runtime/trace, how does a task differ from a region, and how is each one ended?
basics
~20 sA runtime/trace task is a logical operation that may span goroutines: NewTask returns a context carrying it, and Task.End closes it. A region is one interval inside a single goroutine, ended by the goroutine that started it.
What has the Go runtime detected when it prints "fatal error: all goroutines are asleep - deadlock!"?
basics
~20 sEvery user goroutine is parked waiting on another goroutine, with nothing runnable, no armed timer and no thread in a system call. Progress is provably impossible, so the runtime dumps every goroutine stack and exits.
What does a "too many open files" error from net.Dial or os.Open tell you?
basics
~20 sThe process has hit its RLIMIT_NOFILE ceiling on open file descriptors. Every socket, file and open HTTP response body costs one, so the cause is almost always a resource opened on some code path and never closed.
How do you dump every goroutine's stack from a running Go service over HTTP?
basics
~10 sImport net/http/pprof for its side effect and serve HTTP on a debug port, then fetch /debug/pprof/goroutine?debug=2. That prints the full current stack of every goroutine in the process, one block each.
For a Go process, how does an inuse_space heap profile differ from the RSS the operating system reports?
basics
~20 sAn inuse_space heap profile counts only live Go heap objects, sampled as of the last completed garbage collection. RSS counts every physical page the kernel has given the whole process, including freed-but-retained heap spans, goroutine stacks and runtime structures.
How do you find out how many OS threads a running Go process has, given runtime.NumGoroutine counts goroutines?
basics
~10 sruntime.NumGoroutine counts goroutines only, never OS threads. For the thread count read the /sched/threads:threads gauge from runtime/metrics, run the process with GODEBUG=schedtrace=1000, or ask the operating system, for example /proc/<pid>/status on Linux.
What does GOTRACEBACK=all change about the output of a crashing Go program?
basics
~20 sBy default a Go crash prints a stack for the goroutine that panicked. GOTRACEBACK=all makes the runtime print a stack for every user-created goroutine instead. It is an environment variable, so it costs a restart, not a rebuild.
When you step through a plain `go build` binary, why do locals read as optimized out, and which build flags fix it?
basics
~20 sGo's compiler keeps locals in registers and inlines small calls, so the variable and the stack frame a debugger looks for do not exist at runtime. Rebuild with go build -gcflags="all=-N -l" to disable optimisation and inlining before stepping.
In Go, how do you capture a panic's stack trace inside the deferred function that recovers it?
basics
~10 sCall runtime/debug.Stack() inside the deferred function. It returns the calling goroutine's formatted stack trace as a byte slice, still containing the frames that panicked. debug.PrintStack() does the same but writes straight to standard error.
How do you enable a Go runtime tracer such as GODEBUG=gctrace=1 without rebuilding the binary?
basics
~20 sGODEBUG is an environment variable the Go runtime reads when the process starts, so you set it in whatever launches the binary: GODEBUG=gctrace=1 ./job. It holds a comma-separated list of name=value settings and needs no code change.
What does the Go runtime do when a process receives SIGQUIT and nothing has trapped it?
basics
~20 sAn untrapped SIGQUIT is fatal to a Go process: the runtime prints a header naming the signal, then a stack trace for every goroutine, and the process then dies. It is a one-shot snapshot, not something the process survives.
What does the GOGC environment variable control in a Go program, and what does its default of 100 mean?
basics
~20 sGOGC sets how much the heap may grow between garbage collections, as a percentage of the live data kept after the last one. The default, 100, lets the heap roughly double before the next collection starts.
What does GOMAXPROCS control, and how does a container's CPU limit affect its default?
basics
~20 sGOMAXPROCS caps how many goroutines run Go code simultaneously. Since Go 1.25 on Linux its default is the lower of the machine's usable logical CPUs and the container's cgroup CPU limit; older releases looked only at the CPU count.
What does Go's GOMEMLIMIT environment variable do, and why is it a soft limit?
basics
~20 sGOMEMLIMIT sets a target ceiling on the total memory the Go runtime manages, so the garbage collector runs more often as usage approaches it. It is soft: the runtime never refuses an allocation, so a program can still exceed it.
Why does raising GOGC from 100 to 400 cut GC CPU time but raise a Go service's peak memory?
basics
~20 sA collection cycle's cost tracks live data, not garbage, so quadrupling the growth allowance runs about a quarter as many cycles for the same allocation rate. The price is the heap the process holds between cycles, which grows roughly fourfold.
Why can a Go service be OOM-killed with GOMEMLIMIT set to the container's memory limit?
basics
~10 sGOMEMLIMIT governs only memory the Go runtime manages, and it is a target rather than a cap. The binary, cgo allocations and mapped files sit outside it, so an equal setting leaves no headroom.
How do you use benchstat to compare Go benchmark results from two revisions of a package?
basics
~20 sRun go test -bench with -count several times on each revision, saving the output to old.txt and new.txt, then run benchstat old.txt new.txt. benchstat pairs benchmarks by name and prints the percent change with a p-value.
What does the Go compiler do differently at a call site that a PGO profile marks hot?
basics
~20 sTwo things: it inlines hot callees it would otherwise judge too expensive, and it devirtualizes hot indirect calls by emitting a direct call to the dominant concrete type behind a type check, with the original indirect call as the fallback. Cold code is left alone.
How do you make `go build` print the Go compiler's inlining and escape-analysis decisions?
basics
~10 sRun go build -gcflags=-m ./... or go test -gcflags=-m. The compiler prints its inlining and escape decisions to stderr: can inline, inlining call to, cannot inline, moved to heap. Use -m=2 for more detail.
Where do you put a Go PGO profile so that `go build` uses it without extra flags?
basics
~20 sSave a pprof CPU profile as a file named default.pgo in the main package's directory. The go command defaults to -pgo=auto, so build, run, install and test pick that file up with no flag. Use -pgo=off to opt out.
In benchstat's comparison output, what do the p-value, the n count, and a ~ in the delta column tell you?
basics
~20 sThe p-value is the chance of seeing a difference this large if both revisions performed the same, and n is the samples per side. A ~ means the difference was not significant at 95% confidence, not that the two are equal.