20. Benchmarks and a first look at pprof
20.1 Writing a benchmark
func BenchmarkMapLookup(b *testing.B) {
m := make(map[int]bool, 10000)
for i := 0; i < 10000; i++ {
m[i] = true
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = m[i%10000]
}
}
go test -bench=. -benchmem ./...
# BenchmarkMapLookup-8 50000000 23.1 ns/op 0 B/op 0 allocs/op
b.N is chosen automatically by the testing framework, running long
enough for a stable measurement. -benchmem adds allocation counts —
often the more actionable number, since allocations drive GC pressure
more than raw CPU time in typical services.
20.2 Comparing implementations
Day 11's exercise — linear scan vs. map lookup vs. sorted-slice binary search — is the right way to answer "which is faster" with data instead of intuition. The honest answer is usually: map wins past a few dozen items; linear scan wins below that because of map's per-lookup hashing overhead and worse cache locality.
20.3 pprof, the five-minute version
import _ "net/http/pprof" // registers /debug/pprof/* handlers as a side effect
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 # CPU
go tool pprof http://localhost:6060/debug/pprof/heap # memory
(pprof) top10
(pprof) web # opens an SVG call graph, requires graphviz
Chapter 37 goes deeper; for now, know that pprof is always one import
away in Go — no separate profiler installation, no sampling agent to
configure.
20.4 Real-world example
"It's slow" is not actionable. "CPU profile shows 40% of time in
regexp.MustCompile because it's being called per-request instead of
once at package init" is a one-line fix, and it's the kind of finding
pprof top10 surfaces in under two minutes against a running service.