Performance Rules: When to Optimize
Measure-first rules and anti-premature-optimization guidance.
Search across all documentation pages
Measure-first rules and anti-premature-optimization guidance.
Use when a PR claims speedups, when latency SLOs slip, or when reviewers suspect premature micro-optimization.
| Step | Action | Stop if |
|---|---|---|
| 1 | Reproduce with realistic load | Cannot reproduce - fix observability first |
| 2 | go test -bench + -benchmem | Noise dominates - stabilize environment |
| 3 | CPU profile (pprof) | Hot func is not in your code - fix deps/config |
| 4 | Heap/allocs profile | Allocs are one-time startup - defer work |
| 5 | Trace (go tool trace) | Issue is IO wait - tune timeouts/parallelism |
| 6 | Code change + recorded delta | Regression in clarity without SLO gain |
| Signal | Likely lever | Verify |
|---|---|---|
| CPU profile >10% in one func | Algorithm, cache, precompute | CPU profile after |
High alloc_space in loop | Preallocate slice/map, strings.Builder | -benchmem |
| GC pause SLO miss | Reduce pointers, reuse buffers, smaller heaps | GODEBUG=gctrace=1 |
| RPC tail latency | Connection pooling, batching, fewer syscalls | Trace + histogram |
| JSON encode hot | json.Encoder reuse, smaller structs, codegen | Benchmark realistic payload |
| Anti-pattern | Why wait | Instead |
|---|---|---|
| No profile attached | Guessing wastes review time | Profile first |
| Micro-optimizing cold paths | No SLO impact | Ship clarity |
unsafe without ADR | Safety and portability cost | Pure Go proof |
| Global object pools everywhere | Complexity and stale state | Pool only proven allocs |
| Disabling checks in prod | Security/reliability risk | Fix root cause |
Premature sync.Pool | Hard to reason | Measure allocs |
| Rule | Apply when | Skip when |
|---|---|---|
| Preallocate slices | Size known from input | Rare append paths |
strings.Builder | Many concatenations in loop | Few joins - use + or Join |
| Pass pointers on large structs | Profiler shows copy cost | Small structs - prefer values |
json.Encoder buffer reuse | High QPS encode | One-off CLI output |
| PGO (profile-guided opt) | Stable workload binary | Fast-changing code paths |
| GOGC tuning | Latency after alloc fixes | Before reducing allocations |
go test -bench=BenchmarkFoo -benchmem -count=5 ./...
go test -cpuprofile=cpu.prof -bench=BenchmarkFoo ./...
go tool pprof -top cpu.prof| Rule | Requirement |
|---|---|
| Realistic input | Production-sized payloads |
| Reset timer | Expensive setup outside loop |
| Compare baseline | Branch A vs B in one PR |
| Document environment | CPU count, Go version, GOMAXPROCS |
| Track in CI lightly | Optional benchstat on release tags |
| Area | Rule |
|---|---|
| HTTP | Set timeouts; reuse Transport with idle conn limits |
| DB | Pool size matched to concurrency; avoid N+1 queries |
| gRPC | Reuse connections; stream when payloads are large |
| Concurrency | Bound workers; profile under -race separately |
| Logging | Structured logs at info; debug off hot paths |
| Metrics | Low-cardinality labels |
| Knob | Use when | Caution |
|---|---|---|
GOGC | Proven allocation-bound pauses | Hides leaks |
GOMEMLIMIT | OOM risk with clear limit | May increase CPU |
| Green Tea GC (1.26 default) | New deployments | Validate on workload |
debug.SetMemoryLimit | Container-aware services | Test rollback |
When bypassing a clarity rule for speed, PR description includes:
It burns review time and adds bugs on cold paths.
Optimize when measurement shows user-visible impact.
When a hot path is proven and the team documents the trade-off with tests guarding behavior.
Only performance PRs or APIs on critical paths.
Otherwise periodic profiling suffices.
May be batch/off-peak work.
Check tail latency histograms, not averages alone.
Hoist setup, use realistic data, run -count=5, compare on same machine class.
Compiler reports help.
Profiles confirm real-world allocation sites.
Stable binaries with representative production CPU profile fed into build.
Revisit each release.
Memory is tighter; clarity still first.
Measure on device targets, not desktop profiles alone.
Use when alloc profile shows repeated large temporaries.
Reset objects on Get; never store authoritative state only in Pool.
Only after alloc reduction and with memory limit aligned to container cap.
Monitor GC CPU overhead.
Effective Go favors clarity.
These rules add when to deviate with evidence.
Use pprof on a representative benchmark or staging load, following the benchmark hygiene section above.
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).
Reviewed by Chris St. John·Last updated Jul 19, 2026