skip to content

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 pageshow

explore

questions

102 · 6 sections

How do you capture a CPU profile from a running Go program with runtime/pprof?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Open 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.

open as a page

What does a blank import of net/http/pprof add to a Go program, and how do you reach it?

level: juniorimportance: must knowfreq 52%
basics
~20 s

A 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.

open as a page

In `go tool pprof`, what does the `top` command list, and what do its flat and cum columns mean?

level: juniorimportance: must knowfreq 76%
basics
~20 s

top 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.

open as a page

How does Go's mutex profile differ from its block profile in what each records?

level: middleimportance: must knowfreq 50%
basics
~20 s

Go'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.

open as a page

In a Go heap profile, what is the difference between inuse_space and alloc_space?

level: middleimportance: must knowfreq 72%
basics
~20 s

inuse_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.

open as a page

In `go tool trace`'s goroutine analysis, what does a goroutine's scheduler wait time mean?

level: juniorimportance: should knowfreq 38%
basics
~20 s

Scheduler 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.

open as a page

In `go tool trace`, what does a goroutine's GC mark assist time mean, and which goroutines pay it?

level: middleimportance: should knowfreq 30%
basics
~20 s

Mark 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.

open as a page

What does the timeline view in go tool trace show, and which other views does it serve?

level: middleimportance: should knowfreq 42%
basics
~20 s

The 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.

open as a page

What does Go's execution tracer actually record, and why keep captures to a few seconds?

level: middleimportance: should knowfreq 38%
basics
~20 s

Go'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.

open as a page

In Go's runtime/trace, how does a task differ from a region, and how is each one ended?

level: middleimportance: should knowfreq 34%
basics
~20 s

A 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.

open as a page

What has the Go runtime detected when it prints "fatal error: all goroutines are asleep - deadlock!"?

level: juniorimportance: must knowfreq 60%
basics
~20 s

Every 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.

open as a page

What does a "too many open files" error from net.Dial or os.Open tell you?

level: juniorimportance: must knowfreq 55%
basics
~20 s

The 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.

open as a page

How do you dump every goroutine's stack from a running Go service over HTTP?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Import 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.

open as a page

For a Go process, how does an inuse_space heap profile differ from the RSS the operating system reports?

level: juniorimportance: should knowfreq 35%
basics
~20 s

An 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.

open as a page

How do you find out how many OS threads a running Go process has, given runtime.NumGoroutine counts goroutines?

level: juniorimportance: should knowfreq 40%
basics
~10 s

runtime.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.

open as a page

What does GOTRACEBACK=all change about the output of a crashing Go program?

level: juniorimportance: must knowfreq 50%
basics
~20 s

By 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.

open as a page

When you step through a plain `go build` binary, why do locals read as optimized out, and which build flags fix it?

level: juniorimportance: must knowfreq 50%
basics
~20 s

Go'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.

open as a page

In Go, how do you capture a panic's stack trace inside the deferred function that recovers it?

level: juniorimportance: must knowfreq 58%
basics
~10 s

Call 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.

open as a page

How do you enable a Go runtime tracer such as GODEBUG=gctrace=1 without rebuilding the binary?

level: juniorimportance: should knowfreq 45%
basics
~20 s

GODEBUG 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.

open as a page

What does the Go runtime do when a process receives SIGQUIT and nothing has trapped it?

level: middleimportance: should knowfreq 44%
basics
~20 s

An 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.

open as a page

What does the GOGC environment variable control in a Go program, and what does its default of 100 mean?

level: juniorimportance: must knowfreq 55%
basics
~20 s

GOGC 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.

open as a page

What does GOMAXPROCS control, and how does a container's CPU limit affect its default?

level: juniorimportance: must knowfreq 58%
basics
~20 s

GOMAXPROCS 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.

open as a page

What does Go's GOMEMLIMIT environment variable do, and why is it a soft limit?

level: juniorimportance: must knowfreq 34%
basics
~20 s

GOMEMLIMIT 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.

open as a page

Why does raising GOGC from 100 to 400 cut GC CPU time but raise a Go service's peak memory?

level: middleimportance: must knowfreq 62%
basics
~20 s

A 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.

open as a page

Why can a Go service be OOM-killed with GOMEMLIMIT set to the container's memory limit?

level: seniorimportance: must knowfreq 52%
basics
~10 s

GOMEMLIMIT 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.

open as a page

How do you use benchstat to compare Go benchmark results from two revisions of a package?

level: juniorimportance: must knowfreq 45%
basics
~20 s

Run 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.

open as a page

What does the Go compiler do differently at a call site that a PGO profile marks hot?

level: middleimportance: must knowfreq 44%
basics
~20 s

Two 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.

open as a page

How do you make `go build` print the Go compiler's inlining and escape-analysis decisions?

level: juniorimportance: should knowfreq 50%
basics
~10 s

Run 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.

open as a page

Where do you put a Go PGO profile so that `go build` uses it without extra flags?

level: juniorimportance: should knowfreq 24%
basics
~20 s

Save 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.

open as a page

In benchstat's comparison output, what do the p-value, the n count, and a ~ in the delta column tell you?

level: middleimportance: should knowfreq 38%
basics
~20 s

The 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.

open as a page