Optimising without measuring wastes time; Go's built-in benchmarks and pprof make measuring easy.
A benchmark is a function func BenchmarkXxx(b *testing.B) in a _test.go file. Run the code under test b.N times; the framework increases b.N until timing is stable:
for range b.N { Parse(input) }(Go 1.24 adds for b.Loop() { ... }, which also prevents the compiler from optimising the work away and excludes setup automatically.)
Run with go test -bench=. -benchmem. Output shows ns/op, and with -benchmem also B/op and allocs/op — allocations are often the real cost. Use b.ReportAllocs(), b.ResetTimer() after expensive setup, and sub-benchmarks with b.Run to compare sizes. Results are noisy: run with -count=10 before and after a change and compare with benchstat (golang.org/x/perf/cmd/benchstat), which tells you whether a difference is statistically significant.
To find where time goes, profile:
go test -bench=Parse -cpuprofile cpu.out -memprofile mem.outgo tool pprof -http=:8081 cpu.outopens a web UI with flame graphs, top functions and annotated source.- In a running service,
import _ "net/http/pprof"registers/debug/pprof/handlers. Thengo tool pprof http://host:port/debug/pprof/profile?seconds=30grabs a CPU profile;/heap,/goroutine,/mutexand/blockshow other views. Don't expose these endpoints publicly.
go tool trace shows scheduler, GC and goroutine timelines when profiles aren't enough. And profile-guided optimisation: put a representative CPU profile at default.pgo in the main package and go build uses it to inline and devirtualise hot paths, typically gaining a few percent.
Example
// join_test.go — go test -bench=. -benchmem
package join
import (
"strings"
"testing"
)
var parts = strings.Fields("a b c d e f g h i j k l m n o p")
func concat(xs []string) string {
s := ""
for _, x := range xs {
s += x
}
return s
}
func BenchmarkConcat(b *testing.B) {
b.ReportAllocs()
for range b.N {
_ = concat(parts) // ~15 allocs/op
}
}
func BenchmarkJoin(b *testing.B) {
b.ReportAllocs()
for range b.N {
_ = strings.Join(parts, "") // 1 alloc/op
}
}Remember
BenchmarkXxx(b *testing.B)+go test -bench=. -benchmem- Compare runs with benchstat, not by eyeballing one result
- pprof (files or net/http/pprof) shows where CPU and memory go
Watch out for
- Benchmarking code whose result is unused, letting the compiler eliminate the work you meant to measure.