Performance Basics
9 examples to get you started with Performance - 7 basic and 2 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Create a module:
mkdir perflab && cd perflab && go mod init example.com/perflab. - Add a test file per example or one
perflab_test.gopackage. - Optional:
go install golang.org/x/perf/cmd/benchstat@latestfor comparing benchmark runs.
Basic Examples
1. Minimal Benchmark
Measure how long a function takes per iteration with testing.B.
package perflab
import "testing"
func Sum(n int) int {
s := 0
for i := 0; i < n; i++ {
s += i
}
return s
}
func BenchmarkSum(b *testing.B) {
for b.Loop() {
Sum(1000)
}
}go test -bench=BenchmarkSum ./...BenchmarkXxxfunctions live in*_test.gofiles.b.Loop()(Go 1.24+) replaces manualb.Nloops and resets timers correctly.- Output shows
ns/op- nanoseconds per loop iteration.
Related: Benchmarks with testing.B & B.Loop - benchmark depth
2. Allocation Stats with -benchmem
Report bytes and allocations per operation alongside timing.
import "strings"
func BenchmarkJoin(b *testing.B) {
parts := []string{"a", "b", "c", "d"}
b.ReportAllocs()
for b.Loop() {
_ = strings.Join(parts, ",")
}
}go test -bench=BenchmarkJoin -benchmem ./...-benchmemaddsB/op(bytes per op) andallocs/opcolumns.b.ReportAllocs()ensures alloc counts appear even without-benchmem.- High
allocs/opin hot paths often matters more than a few nanoseconds.
Related: Slice Preallocation, strings.Builder & map Pre-sizing - reducing allocs
3. CPU Profile from a Benchmark
Write a CPU profile file while benchmarks run.
go test -cpuprofile=cpu.prof -bench=BenchmarkJoin ./...
go tool pprof -top cpu.prof-cpuprofilesamples the process during the benchmark only.go tool pprof -topprints the hottest functions by flat CPU time.- Use this before optimizing - confirm the function you plan to change is actually hot.
Related: CPU & Heap Profiling with pprof - full pprof workflow
4. Heap Profile from Tests
Capture heap allocation sites during a test or benchmark.
go test -memprofile=heap.prof -bench=BenchmarkJoin ./...
go tool pprof -top -alloc_space heap.prof-memprofilerecords allocation samples.-alloc_spacesorts by total bytes allocated;-inuse_spaceshows live heap.- Pair with
-benchmemto connect micro numbers to call sites.
Related: CPU & Heap Profiling with pprof - reading heap graphs
5. net/http/pprof Endpoint
Expose profiles on a running HTTP server for load-test collection.
package main
import (
_ "net/http/pprof"
"net/http"
)
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// ... your service on another port
}go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30- Import
_ "net/http/pprof"registers/debug/pprof/*onDefaultServeMux. - Bind
6060on localhost or protect with auth in production. - Collect profiles while load generators run, not on an idle server.
Related: Performance in Go: Measure, Profile, Then Optimize - measurement culture
6. Interactive pprof Web UI
Explore flame graphs in the browser.
go tool pprof -http=:8081 cpu.prof- Opens a local web UI with flame graph, top, peek, and source views.
- Use
FocusandIgnoreto narrow noisy stdlib frames. - Share the profile file in PRs as evidence for optimization claims.
Related: CPU & Heap Profiling with pprof - flame graph reading
7. GC Trace with GODEBUG
Print garbage collector timing and heap size to stderr.
GODEBUG=gctrace=1 go run .- Each line logs GC cycle, pause, and heap goal.
- Useful when tail latency spikes correlate with GC, not CPU hotspots.
- Reduce allocation rate before tuning
GOGCorGOMEMLIMIT.
Related: GC Tuning for Latency-Sensitive Services - GC experiments
Intermediate Examples
8. Compare Runs with benchstat
Record baseline and candidate benchmark output, then compare statistically.
go test -bench=BenchmarkJoin -count=10 ./... > old.txt
# change code
go test -bench=BenchmarkJoin -count=10 ./... > new.txt
benchstat old.txt new.txt-count=10reduces noise from CPU frequency scaling and cache state.benchstatreports whether deltas are significant, not just faster on one run.- Document environment (CPU model,
GOMAXPROCS) when publishing numbers.
Related: Benchmarks with testing.B & B.Loop - bench hygiene
9. Dedicated pprof Server in Production Shape
Mount pprof on a custom mux with timeouts and localhost binding.
package main
import (
"net/http"
_ "net/http/pprof"
"time"
)
func main() {
admin := http.NewServeMux()
admin.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
// pprof routes registered on DefaultServeMux; mount them:
admin.Handle("/debug/", http.DefaultServeMux)
srv := &http.Server{
Addr: "127.0.0.1:6060",
Handler: admin,
ReadHeaderTimeout: 5 * time.Second,
}
_ = srv.ListenAndServe()
}- Separate admin listener keeps debug endpoints off the public API port.
ReadHeaderTimeoutblocks slowloris on the admin port too.- In Kubernetes, port-forward
6060during incidents instead of exposing it publicly.
Related: net/http Basics - server patterns
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).