go-performance-review
Original:🇺🇸 English
Translated
Detect performance anti-patterns and apply optimization techniques in Go. Covers allocations, string handling, slice/map preallocation, sync.Pool, benchmarking, and profiling with pprof. Use when checking performance, finding slow code, reducing allocations, profiling, or reviewing hot paths. Trigger examples: "check performance", "find slow code", "reduce allocations", "benchmark this", "profile", "optimize Go code". Do NOT use for concurrency correctness (use go-concurrency-review) or general code style (use go-coding-standards).
17installs
Added on
NPX Install
npx skill4agent add eduardo-sl/go-agent-skills go-performance-reviewTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Go Performance Review
Profile first, optimize second. Never optimize without a benchmark proving the problem.
1. Allocation Reduction
Prefer strconv
over fmt
for primitive conversions:
strconvfmtgo
// ✅ Good — zero allocations for simple conversions
s := strconv.Itoa(42)
s := strconv.FormatFloat(3.14, 'f', 2, 64)
// ❌ Bad — fmt.Sprintf allocates
s := fmt.Sprintf("%d", 42)Avoid unnecessary string-to-byte conversions:
go
// ✅ Good — use strings.Builder for concatenation
var b strings.Builder
for _, s := range parts {
b.WriteString(s)
}
result := b.String()
// ❌ Bad — repeated concatenation allocates on every +
result := ""
for _, s := range parts {
result += s
}Preallocate slices and maps when size is known:
go
// ✅ Good — single allocation
users := make([]User, 0, len(ids))
for _, id := range ids {
users = append(users, getUser(id))
}
// ✅ Good — map with capacity hint
lookup := make(map[string]User, len(users))
// ❌ Bad — repeated growing
var users []User // starts at 0, grows via doublingUse sync.Pool
for frequently allocated, short-lived objects:
sync.Poolgo
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func process(data []byte) string {
buf := bufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufPool.Put(buf)
}()
buf.Write(data)
return buf.String()
}2. Hot Path Optimizations
Avoid interface conversions in tight loops:
go
// ✅ Good — concrete type in loop
func sum(vals []int64) int64 {
var total int64
for _, v := range vals {
total += v
}
return total
}
// ❌ Bad — interface{} causes boxing/unboxing
func sum(vals []interface{}) int64 { ... }Avoid reflect
in performance-critical paths:
reflectIf you need reflection-like behavior at scale, use code generation
(, , protocol buffers).
go generatestringerReduce pointer chasing:
go
// ✅ Good — contiguous memory, cache-friendly
type Points struct {
X []float64
Y []float64
}
// ❌ Slower — pointer chasing per element
type Points []*Point3. Map Performance
go
// ✅ Use capacity hints
m := make(map[string]int, expectedSize)
// ✅ For read-heavy concurrent access, use sync.Map
// But ONLY when keys are stable — sync.Map has higher overhead
// for writes than a mutex-protected map.
// ✅ For fixed key sets, consider using a slice with index mapping
// instead of a map.4. Benchmarking
ALWAYS write benchmarks before and after optimization:
go
func BenchmarkFoo(b *testing.B) {
// Setup outside the loop
input := generateInput()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result = Foo(input) // assign to package-level var to prevent elision
}
}
// Package-level var prevents compiler from eliminating the call
var result stringRun benchmarks with memory profiling:
bash
go test -bench=BenchmarkFoo -benchmem -count=5 ./...Compare before/after with :
benchstatbash
go test -bench=. -count=10 > old.txt
# make changes
go test -bench=. -count=10 > new.txt
benchstat old.txt new.txt5. Profiling
CPU profiling:
bash
go test -cpuprofile=cpu.prof -bench=BenchmarkFoo .
go tool pprof cpu.profMemory profiling:
bash
go test -memprofile=mem.prof -bench=BenchmarkFoo .
go tool pprof -alloc_space mem.profHTTP server profiling (import net/http/pprof):
go
import _ "net/http/pprof"
// Access at http://localhost:6060/debug/pprof/
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()6. Common Anti-Patterns
| Anti-Pattern | Fix |
|---|---|
| |
| String concatenation in loop | |
| Slice without preallocation | |
| Map without capacity hint | |
| Compile once at package level |
| Use code-gen ( |
| Logging in tight loop | Batch or sample |
| Manual cleanup (rare, benchmark first) |
Important Caveat
Most Go code is not performance-critical. Readability and correctness ALWAYS
take priority over micro-optimizations. Only apply these patterns when:
- A benchmark proves this code path is a bottleneck
- The optimization is significant (>10% improvement)
- The resulting code remains readable and maintainable
Premature optimization is still the root of all evil, even in Go.