Overview
Profiling is built into the Go runtime, not bolted on: any binary can emit CPU, memory,
and contention profiles in the pprof protobuf format, and go tool pprof analyses them.
Overhead is low enough to sample production continuously, and since Go 1.21 those same
production profiles feed back into the compiler as profile-guided optimization. This is
the first tool to reach for on "where does the time/memory go" questions; for "where does
the latency go", see the execution tracer.
Key points
- Two collection routes:
runtime/pprofwrites profiles programmatically (andgo test -cpuprofile/-memprofilefor benchmarks); importingnet/http/pprofregisters HTTP endpoints under/debug/pprof/for on-demand capture from a live service. - CPU profile: sampled at ~100 Hz; captures stacks of running goroutines over a
duration (
/debug/pprof/profile?seconds=30). - heap / allocs: heap shows live objects (
inuse_space, default) — use for memory growth; allocs shows cumulative allocation since start (alloc_space) — use for GC pressure. Same data, different default sample index. - goroutine: stack dump of every goroutine — the tool for leak hunting (watch the count grow, group by creation site).
- block / mutex: time blocked on channels/selects and mutex contention; both off by
default — enable with
runtime.SetBlockProfileRate/runtime.SetMutexProfileFraction. go tool pprof:top(hotspots, flat vs cum),list <func>(annotated source),peek,web(SVG call graph);-http=:8080opens the interactive UI with flame graph, graph, and source views — the mode worth defaulting to.- Comparisons:
-base old.pb.gz new.pb.gzdiffs two profiles — the honest way to judge an optimization or spot a regression between releases. - Continuous profiling: sampling overhead is low single-digit percent, so fleets profile always-on (Parca, Pyroscope, Grafana, cloud profilers) — turning "it was slow at 3am" into a profile you can open.
- PGO (Go 1.21+): drop a CPU profile as
default.pgonext tomainandgo builduses it to guide inlining and devirtualization; typical gains a few percent for free, from real production behaviour.
Details
Reading the numbers
flat is time in the function itself; cum includes callees. A tall flame-graph tower
with a wide base in runtime.mallocgc means allocation pressure — switch to the allocs
profile and hunt the allocation sites rather than micro-optimizing the CPU view. Symbols
survive in stripped release binaries; pprof needs the binary (or its symbol table) only
for list/disassembly views.
Serving pprof safely
import _ "net/http/pprof" registers on http.DefaultServeMux — never expose that mux
publicly. Serve it on a separate internal listener (or an admin port) instead; profiles
leak function names, and the endpoints can be used to degrade the service.
Examples
# Capture 30s of CPU from a live service, open the web UI
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/profile?seconds=30"
# Live-heap growth investigation
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap
# Benchmark-driven, then diff against a baseline
go test -bench=. -cpuprofile new.pb.gz ./ring
go tool pprof -base old.pb.gz new.pb.gz
Related
- The go command & tool catalog — parent catalog of the toolchain.
- Execution tracer — the complementary view: pprof finds CPU hotspots, the tracer explains latency.
- go test — benchmarks are the usual local profile source (
-cpuprofile,-memprofile). - Performance engineering — the discipline these profiles serve: measure, don't guess.
- JFR & JMC — the JVM counterpart: event-based flight recording where Go splits sampling (pprof) from tracing.
- Performance & load testing — profiles under synthetic load close the loop before production does.