
Go Performance
- 1 installs
- 17 repo stars
- Updated July 16, 2026
- blacktop/dotfiles
go-performance is a Claude Code skill that measures and improves Go program performance using benchmarks, benchstat, pprof, trace, and PGO.
About
go-performance is a Claude Code skill for measuring and improving Go program performance using a current Go 1.26-era workflow. It insists on measurement before rewriting: name the metric that matters, add or repair a benchmark, compare runs with benchstat, collect one profile at a time, fix the dominant cost, then re-measure. A developer uses it when profiling Go code, diagnosing CPU or memory bottlenecks, writing benchmarks, or applying PGO to hot paths.
- Measure-first Go performance workflow using benchmarks, benchstat, pprof, and trace before rewriting
- Targets specific metrics (ns/op, B/op, allocs/op, tail latency) and fixes the dominant cost
- Covers Go 1.26-era posture: b.Loop(), PGO, flight recorder, and container-aware GOMAXPROCS
Go Performance by the numbers
- 1 all-time installs (skills.sh)
- Ranked #79 of 98 Go skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
go-performance capabilities & compatibility
- Capabilities
- performance profiling · benchmarking · optimization
- Use cases
- debugging · refactoring
What go-performance says it does
Measure and improve Go program performance using current Go 1.26-era workflow.
Start with measurement, not rewriting.
Run the benchmark repeatedly and compare with `benchstat`; do not trust one run.
npx skills add https://github.com/blacktop/dotfiles --skill go-performanceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 17 |
| Last updated | July 16, 2026 |
| Repository | blacktop/dotfiles ↗ |
What it does
Profile and improve Go performance by measuring bottlenecks with benchmarks and pprof before changing code.
Who is it for?
Go developers tuning hot-path code with benchmark and profile evidence.
Skip if: Optimizing code before there is a measured, reproduced performance problem.
When should I use this skill?
Profiling Go code, diagnosing CPU or memory bottlenecks, writing benchmarks, or applying PGO.
What you get
The dominant cost is identified from profiles and benchmarks, fixed, and validated with before/after benchmark deltas.
- identified bottleneck with evidence
- before/after benchmark or profile deltas
- residual risks and version assumptions
By the numbers
- 8-step default workflow
- targets Go 1.26-era workflow
Files
Go Performance
Start with measurement, not rewriting.
Read the right reference
- Read references/measurement.md for benchmark setup,
go testflags,pprof, trace, flight recording, runtime metrics, and PGO workflow. - Read references/optimization.md when you are changing code after measurement or reviewing hot-path code.
Default workflow
1. Reproduce the problem and name the metric that matters: ns/op, B/op, allocs/op, throughput, tail latency, pause time, goroutine growth, or CPU saturation. 2. Add or repair a benchmark before changing code. On Go 1.24+ prefer b.Loop() for new or edited benchmarks unless the repo must support older Go. 3. Run the benchmark repeatedly and compare with benchstat; do not trust one run. 4. Collect one diagnostic at a time: CPU, heap/allocs, mutex, block, or trace. Do not mix profiles unless you must; diagnostics can distort each other. 5. Fix the dominant cost first: algorithmic complexity, redundant work, bad data layout, excess allocation, or contention. 6. Re-run the same benchmark and compare with benchstat. 7. Apply PGO only after the code path is correct and the profile is representative. 8. Validate the change under realistic service conditions with runtime metrics, net/http/pprof, or flight recording if the issue is production-only.
Rules of engagement
- Prefer algorithmic or architectural fixes over stylistic micro-optimizations.
- Use benchmark evidence and profiles to justify code complexity.
- For long-running services, profile the service shape you actually run; microbenchmarks alone are not enough.
- Use
-run='^$'when you want benchmark-only runs. - For contention or scheduler issues, use trace, block, and mutex tooling instead of only CPU profiles.
- For intermittent production latency, consider the Go 1.25+ flight recorder before building custom tracing machinery.
Go 1.26-specific posture
- Re-measure old workarounds on Go 1.26 before preserving them. Go 1.26 changed the runtime and compiler enough that some older allocation, cgo, and GC workarounds may no longer pay for their complexity.
- On Linux containers, remember that Go 1.25+ made
GOMAXPROCScontainer-aware by default. Do not cargo-cultautomaxprocsinto modern Go services without a measured reason. - Use
testing.T.ArtifactDirplusgo test -artifacts -outputdir ...when a benchmark or perf regression test needs to retain profiles, traces, or other debugging output.
Output expectations
When reporting findings or a fix:
1. State the bottleneck and the evidence. 2. State the specific change and why it should move the measured metric. 3. Report before/after benchmark or profile deltas. 4. Call out residual risks, version assumptions, or production-only gaps.
interface:
display_name: "Go Performance"
short_description: "Profile and optimize Go performance issues"
default_prompt: "Use $go-performance to benchmark, profile, and optimize this Go hot path."
Measurement Workflow
Source snapshot: refreshed 2026-03-12 from official Go 1.26 docs and blog posts
- Go 1.26 release notes: https://go.dev/doc/go1.26
- Diagnostics overview: https://go.dev/doc/diagnostics
testing.B.Loop: https://go.dev/blog/testing-b-loopruntime/pprof: https://pkg.go.dev/runtime/pprofnet/http/pprof: https://pkg.go.dev/net/http/pprofruntime/traceand flight recorder: https://pkg.go.dev/runtime/trace and https://go.dev/blog/flight-recorder- PGO: https://go.dev/doc/pgo
benchstat: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat
Benchmark first
Prefer a targeted benchmark before changing code.
For new or updated benchmarks on Go 1.24+:
func BenchmarkFoo(b *testing.B) {
fixture := newFixture()
for b.Loop() {
foo(fixture)
}
}Why b.Loop:
- excludes setup and cleanup from timing
- avoids most manual timer management
- helps prevent dead-code elimination surprises
Use b.RunParallel for concurrent code paths and pair it with go test -cpu.
Standard benchmark commands
Collect stable results before and after a change:
go test -run='^$' -bench='^BenchmarkFoo$' -benchmem -count=10 ./pkg > before.txt
go test -run='^$' -bench='^BenchmarkFoo$' -benchmem -count=10 ./pkg > after.txt
benchstat before.txt after.txtUseful variations:
go test -run='^$' -bench='^BenchmarkFoo$' -benchmem -benchtime=500ms ./pkg
go test -run='^$' -bench='^BenchmarkFoo$' -benchmem -benchtime=100x ./pkg
go test -run='^$' -bench='^BenchmarkParallelFoo$' -benchmem -cpu=1,2,4 ./pkgRules:
- Use
-count=10or more before trustingbenchstat. - Use
-benchmemfor almost every optimization pass. - Do not mix
-race, coverage, or unrelated noisy tests into performance measurement runs.
Install benchstat if needed:
go install golang.org/x/perf/cmd/benchstat@latestProfile one dimension at a time
The Go docs explicitly warn that diagnostics can interfere with each other. Collect focused data.
CPU
go test -run='^$' -bench='^BenchmarkFoo$' -cpuprofile=cpu.pprof ./pkg
go tool pprof -top -cum cpu.pprof
go tool pprof -http=:0 cpu.pprofHeap and allocs
go test -run='^$' -bench='^BenchmarkFoo$' -memprofile=mem.pprof ./pkg
go tool pprof -top -sample_index=alloc_space mem.pprof
go tool pprof -top -sample_index=alloc_objects mem.pprofUse -memprofilerate=1 only when you need more precise allocation data and can tolerate the extra overhead.
Mutex and block contention
go test -run='^$' -bench='^BenchmarkFoo$' -mutexprofile=mutex.pprof -mutexprofilefraction=1 ./pkg
go test -run='^$' -bench='^BenchmarkFoo$' -blockprofile=block.pprof ./pkg
go tool pprof -top mutex.pprof
go tool pprof -top block.pprofTrace
Use trace for scheduler, latency, blocking, and concurrency-path issues:
go test -run='^$' -bench='^BenchmarkFoo$' -trace=trace.out ./pkg
go tool trace trace.outService profiling
For a long-running service, prefer net/http/pprof or runtime/pprof.
import _ "net/http/pprof"go tool pprof 'http://localhost:6060/debug/pprof/profile?seconds=30'
go tool pprof http://localhost:6060/debug/pprof/heap
curl -o trace.out 'http://localhost:6060/debug/pprof/trace?seconds=5'
go tool trace trace.outNotes:
net/http/pprofendpoints must be requested withGET.- heap, allocs, mutex, block, and goroutine endpoints support
seconds=Ndelta profiles - CPU and trace endpoints use
seconds=Nas capture duration
Flight recorder
Use the Go 1.25+ flight recorder when latency incidents are intermittent and you need the last few seconds before failure rather than a constantly running full trace.
It is a better fit than ad hoc tracing when:
- the service is long-running
- the trigger is rare or unpredictable
- you need scheduler and goroutine context just before the incident
Runtime metrics
Use runtime/metrics for low-overhead continuous observation in production.
High-signal keys:
/gc/heap/live:bytes/gc/heap/goal:bytes/gc/gomemlimit:bytes/cpu/classes/gc/total:cpu-seconds/sched/goroutines:goroutines/sched/latencies:seconds/sync/mutex/wait/total:seconds/cgo/go-to-c-calls:calls
Use these to validate that a benchmark win also improves the live system shape.
PGO
Go PGO consumes representative CPU pprof profiles.
Typical workflow:
curl -o cpu.pprof 'http://localhost:6060/debug/pprof/profile?seconds=30'
go build -pgo=cpu.pprof ./cmd/serverOr place a representative profile at default.pgo in the main package directory and let go build pick it up automatically.
Rules:
- use representative production traffic when possible
- use benchmark-generated profiles only when they are truly representative
- do not expect PGO to rescue bad algorithms or contention bugs
- re-run benchmarks after enabling PGO; keep it only if it helps your workload
Artifact handling in tests
If a test or benchmark emits traces, profiles, or logs for later inspection, use t.ArtifactDir() or b.ArtifactDir() and run:
go test -artifacts -outputdir "$PWD/test-output" ./...This keeps performance evidence attached to the failing or interesting test instead of scattering files across the repo.
Optimization Heuristics
This file combines current Go 1.26-era practice with the strongest hot-path guidance from the ipsw go-performance skill.
Fix in this order
1. Eliminate unnecessary work. 2. Improve algorithmic complexity or batching. 3. Reduce allocations and copying. 4. Reduce lock contention or scheduler stalls. 5. Re-check whether PGO improves the already-good version. 6. Apply micro-optimizations only on measured hot paths.
Go 1.26 reality check
Before preserving complex old workarounds, re-measure on Go 1.26:
- Green Tea GC is now on by default.
- baseline cgo overhead is lower.
- the compiler can place more slice backing stores on the stack.
Practical effect:
- keep
sync.Pool, manual reuse, and cgo batching only when benchmarks still justify them - remove cargo-culted allocation avoidance if the current compiler/runtime already made it cheap
Allocation and escape work
If profiles show allocation pressure:
- preallocate slices and maps when final size is known
- reduce temporary objects in inner loops
- inspect escape and inlining output when needed:
go test -gcflags=all=-m=2 ./pkg 2>&1 | rg 'escapes to heap|moved to heap|cannot inline'Use compiler diagnostics to explain an allocation you already measured, not as a substitute for profiling.
Hot-path patterns worth carrying forward
Use these only when the benchmark or profile points at them.
Prefer strconv over fmt for primitive string conversion
// slower
s := fmt.Sprint(n)
// faster
s := strconv.Itoa(n)Avoid repeated string to []byte conversions in loops
// slower
for b.Loop() {
w.Write([]byte("hello"))
}
// faster
data := []byte("hello")
for b.Loop() {
w.Write(data)
}Pre-size slices and maps
items := make([]T, 0, n)
index := make(map[string]T, n)Pass small values directly
Do not pass pointers just to avoid copying a string or interface-sized value. The indirection can be more expensive and makes escape behavior worse.
Bad examples:
*string*io.Reader
Keep pointer parameters when mutation, identity, or large-struct copying is the real requirement.
Concurrency and contention
If CPU is low but latency is bad, suspect waiting rather than compute:
- inspect mutex and block profiles
- inspect trace for runnable goroutine buildup and scheduler delay
- benchmark parallel paths with
b.RunParalleland-cpu
On Linux containers, do not assume GOMAXPROCS needs manual tuning first. Go 1.25+ already accounts for CPU quota by default. Measure before adding compatibility shims.
Memory limit tuning
If the service fights memory pressure, use GOMEMLIMIT or runtime/debug.SetMemoryLimit deliberately and verify the effect with runtime metrics.
Be careful:
- a memory limit that is too low can force the GC to run almost continuously
- the Go memory limit does not include memory owned outside the Go runtime, such as C allocations or
syscall.Mmap
What good looks like
A solid optimization change usually has all of these:
- a benchmark or production metric that reproduces the problem
- a profile or trace that isolates the dominant cost
- a targeted code change with a simple explanation
- a
benchstatcomparison or production delta showing improvement - no extra complexity that lacks measured payoff
Related skills
FAQ
What is the first step in go-performance's workflow?
Reproduce the problem and name the metric that matters, such as ns/op, B/op, allocs/op, throughput, tail latency, or pause time; measurement comes before rewriting.
When should PGO be applied?
Only after the code path is correct and the profile is representative.