Profiling & Performance
Go ships production-grade profiling tools in the standard library — there’s rarely a need to reach for third-party APM just to answer “where is the time going?”
Benchmarks first
Section titled “Benchmarks first”Before optimizing anything, measure it with go test -bench:
func BenchmarkParseJSON(b *testing.B) { data := []byte(`{"name":"Ada","age":30}`) for i := 0; i < b.N; i++ { var u User json.Unmarshal(data, &u) }}go test -bench=. -benchmem-benchmem adds allocations-per-operation to the output — often the more actionable number, since allocation pressure drives GC overhead.
CPU and memory profiles
Section titled “CPU and memory profiles”Add pprof to any long-running server with a one-line import:
import _ "net/http/pprof"
// somewhere in main:go http.ListenAndServe("localhost:6060", nil)Then, while the program is under load:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30go tool pprof http://localhost:6060/debug/pprof/heapInside the interactive pprof shell, top shows the hottest functions and web renders an SVG call graph (needs Graphviz installed).
Benchmark-driven profiles
Section titled “Benchmark-driven profiles”For a specific function rather than a whole running server:
go test -bench=BenchmarkParseJSON -cpuprofile=cpu.out -memprofile=mem.outgo tool pprof cpu.outThe race detector
Section titled “The race detector”Data races don’t always crash — they can silently corrupt data. Catch them at test time:
go test -race ./...go run -race main.goThe race detector instruments every memory access and slows execution significantly, so it’s normally used in CI and local testing, not production.
Common wins, in order of likelihood
Section titled “Common wins, in order of likelihood”- Reduce allocations — reuse buffers with
sync.Pool, pre-size slices/maps withmake(..., 0, n). - Avoid unnecessary interface boxing in hot loops — concrete types avoid an allocation + indirection.
- Batch I/O — one query for 100 rows beats 100 queries.
- Only then consider more goroutines — concurrency helps with I/O-bound waits, not CPU-bound work beyond your core count.