Use when profiling Go applications (pprof), running benchmarks, optimizing memory/CPU usage, or debugging performance bottlenecks in production Go code.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
Use when profiling Go applications (pprof), running benchmarks, optimizing memory/CPU usage, or debugging performance bottlenecks in production Go code.
disable-model-invocation
true
Go Performance Optimization
Overview
This skill provides comprehensive guidance for profiling, benchmarking, and optimizing Go applications. Use this skill when working on performance-critical code, investigating bottlenecks, or optimizing production systems.
# Profile a test
go test -cpuprofile=cpu.prof -bench=.
# Profile a binary
go test -c
./myapp.test -test.cpuprofile=cpu.prof -test.bench=.
Analysis Commands:
# Interactive web UI (recommended)
go tool pprof -http=:8080 cpu.prof
# Text output - top functions by CPU time
go tool pprof -top cpu.prof
# Top 20 with cumulative time
go tool pprof -top -cum cpu.prof | head -20
# Call graph visualization
go tool pprof -svg cpu.prof > cpu.svg
# Focus on specific function
go tool pprof -focus=processData cpu.prof
# Exclude standard library
go tool pprof -ignore=runtime cpu.prof
Interpreting CPU Profiles:
flat: Time spent in function itself (excludes callees)
flat%: Percentage of total runtime
sum%: Cumulative percentage
cum: Time spent in function and callees
cum%: Cumulative time percentage
Example Output:
Showing nodes accounting for 2.50s, 83.33% of 3.00s total
flat flat% sum% cum cum%
0.80s 26.67% 26.67% 1.20s 40.00% processData
0.60s 20.00% 46.67% 0.90s 30.00% parseJSON
0.50s 16.67% 63.34% 0.50s 16.67% validateInput
Focus optimization on functions with high flat (own time) or cum (total time).
1.2 Memory Profiling
Heap Profiling:
import (
"os""runtime/pprof"
)
funccaptureHeapProfile() {
f, err := os.Create("mem.prof")
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer f.Close()
// Force GC before capturing heap
runtime.GC()
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
}
}
Memory Profiling via CLI:
# Profile memory allocations during test
go test -memprofile=mem.prof -bench=.
# Run benchmark multiple times for stable results
go test -memprofile=mem.prof -bench=. -benchtime=10s
Analysis Commands:
# Web UI showing allocation sites
go tool pprof -http=:8080 mem.prof
# Top allocators
go tool pprof -top mem.prof
# Focus on allocations (inuse_space)
go tool pprof -sample_index=inuse_space -top mem.prof
# Focus on allocation counts (inuse_objects)
go tool pprof -sample_index=inuse_objects -top mem.prof
# Show cumulative allocations (alloc_space)
go tool pprof -sample_index=alloc_space -top mem.prof
# Compare two profiles (before/after)
go tool pprof -base=before.prof after.prof
go tool pprof -http=:8080 goroutine.prof
go tool pprof -top goroutine.prof
Goroutine Leak Indicators:
Steadily increasing goroutine count
Many goroutines blocked on channel recv/send
Goroutines without termination mechanism
1.4 HTTP Profiling Endpoint (Production-Safe)
Enable pprof HTTP Server:
import (
_ "net/http/pprof""net/http"
)
funcmain() {
// Start pprof server on separate port (localhost only)gofunc() {
log.Println("pprof server listening on localhost:6060")
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Your application here
runServer()
}
Access Profiles via HTTP:
# CPU profile (30 seconds)
curl http://localhost:6060/debug/pprof/profile?seconds=30 > cpu.prof
# Heap profile
curl http://localhost:6060/debug/pprof/heap > heap.prof
# Goroutine profile
curl http://localhost:6060/debug/pprof/goroutine > goroutine.prof
# Analyze immediately
go tool pprof http://localhost:6060/debug/pprof/profile
# Web UI
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile
// Only expose on localhost
http.ListenAndServe("localhost:6060", nil)
// Or use SSH port forwarding// ssh -L 6060:localhost:6060 user@production-host// Then access http://localhost:6060/debug/pprof/
2. Benchmarking
2.1 Basic Benchmarks
Simple Benchmark:
funcBenchmarkStringConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
result := "hello" + " " + "world"
_ = result // Prevent compiler optimization
}
}
Benchmark with Setup:
funcBenchmarkProcessData(b *testing.B) {
data := generateTestData(1000)
b.ResetTimer() // Exclude setup timefor i := 0; i < b.N; i++ {
processData(data)
}
}
Running Benchmarks:
# Run all benchmarks
go test -bench=.
# Run specific benchmark
go test -bench=BenchmarkStringConcat
# Benchmark with memory statistics
go test -bench=. -benchmem
# Run multiple iterations for stability
go test -bench=. -count=5
# Longer benchmark time for accurate results
go test -bench=. -benchtime=10s
# CPU profile during benchmark
go test -bench=. -cpuprofile=cpu.prof
2.2 Sub-Benchmarks
Compare Multiple Implementations:
funcBenchmarkStringBuilding(b *testing.B) {
items := []string{"hello", "world", "foo", "bar"}
b.Run("Concat", func(b *testing.B) {
for i := 0; i < b.N; i++ {
result := ""for _, item := range items {
result += item
}
_ = result
}
})
b.Run("StringBuilder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for _, item := range items {
sb.WriteString(item)
}
_ = sb.String()
}
})
b.Run("Join", func(b *testing.B) {
for i := 0; i < b.N; i++ {
result := strings.Join(items, "")
_ = result
}
})
}
// Bad: 2 allocationsfuncprocess(data string)string {
upper := strings.ToUpper(data) // 1 allocreturn strings.TrimSpace(upper) // 1 alloc
}
// Better: 1 allocation (reuse buffer)funcprocess(data string)string {
var sb strings.Builder
sb.Grow(len(data))
for _, r := range data {
if !unicode.IsSpace(r) {
sb.WriteRune(unicode.ToUpper(r))
}
}
return sb.String()
}
2.4 Benchmark Analysis with benchstat
Compare Before/After:
# Baseline
go test -bench=. -count=10 > old.txt
# After optimization
go test -bench=. -count=10 > new.txt
# Statistical comparison
go install golang.org/x/perf/cmd/benchstat@latest
benchstat old.txt new.txt
Example Output:
name old time/op new time/op delta
StringConcat-8 3.24µs ± 2% 0.82µs ± 1% -74.69% (p=0.000 n=10+10)
name old alloc/op new alloc/op delta
StringConcat-8 96.0B ± 0% 64.0B ± 0% -33.33% (p=0.000 n=10+10)
name old allocs/op new allocs/op delta
StringConcat-8 5.00 ± 0% 1.00 ± 0% -80.00% (p=0.000 n=10+10)
// Bad: Creates new string on every iterationfuncbadConcat(items []string)string {
result := ""for _, item := range items {
result += item // New allocation each time
}
return result
}
Solution: strings.Builder (O(N)):
// Good: Single allocation with growthfuncgoodConcat(items []string)string {
var sb strings.Builder
// Pre-allocate if size known
totalLen := 0for _, item := range items {
totalLen += len(item)
}
sb.Grow(totalLen)
for _, item := range items {
sb.WriteString(item)
}
return sb.String()
}
Benchmark: 50x faster for 100 concatenations
Builder Methods:
var sb strings.Builder
sb.WriteString("hello") // Write string
sb.WriteByte('!') // Write single byte
sb.WriteRune('✓') // Write rune (Unicode)
sb.Grow(100) // Pre-allocate capacity
result := sb.String() // Get final string
sb.Reset() // Reuse builder
3.3 Escape Analysis
View Escape Decisions:
go build -gcflags='-m -m' main.go 2>&1 | grep "escapes to heap"
Stack vs Heap:
// Stack allocated (fast)funcsumArray()int {
data := [100]int{} // Stack
sum := 0for _, v := range data {
sum += v
}
return sum
}
// Heap allocated (slower, escapes)funccreateData() *Data {
data := &Data{} // Escapes: pointer returnedreturn data
}
Common Escape Scenarios:
// 1. Returning pointer to local variablefuncescape1() *int {
x := 42return &x // Escapes
}
// 2. Interface conversionfuncescape2()interface{} {
x := 42return x // Escapes (interface)
}
// 3. Storing in interface fieldfuncescape3(data interface{}) {
globalVar = data // Escapes
}
// 4. Size too large for stackfuncescape4() {
data := make([]byte, 1<<20) // 1MB, escapes
_ = data
}
// 5. Slice append beyond capacityfuncescape5() {
data := make([]int, 0, 10)
for i := 0; i < 100; i++ {
data = append(data, i) // May escape
}
}
Reducing Escapes:
// Before: Escapes to heapfor _, item := range items {
result := &Result{Value: item}
process(result)
}
// After: Stack allocated (if process doesn't store it)var result Result
for _, item := range items {
result.Value = item
process(&result)
}
// Bad: O(N²) complexityfuncbuildString(items []string)string {
result := ""for _, item := range items {
result += item // New allocation each iteration
}
return result
}
Solution:
// Good: O(N) complexityfuncbuildString(items []string)string {
var sb strings.Builder
for _, item := range items {
sb.WriteString(item)
}
return sb.String()
}
5.2 Unnecessary Allocations
Anti-Pattern 1: Creating Pointers in Loops:
// Bad: N allocationsfor _, item := range items {
ptr := &item
process(ptr)
}
// Good: Reuse pointervar ptr *Item
for i := range items {
ptr = &items[i]
process(ptr)
}
Anti-Pattern 2: Converting to Interface:
// Bad: Causes allocationfuncprintAll(items []MyStruct) {
for _, item := range items {
fmt.Println(item) // Interface conversion
}
}
// Better: Pass pointer to avoid copyfuncprintAll(items []MyStruct) {
for i := range items {
fmt.Println(&items[i])
}
}
5.3 Defer Overhead in Hot Paths
Anti-Pattern:
// Bad: Defer has overhead in hot loopsfuncprocessMany(items []Item) {
for _, item := range items {
mu.Lock()
defer mu.Unlock() // Accumulates, never runs until function exits
process(item)
}
}
Solution:
// Good: Manual unlock in loopfuncprocessMany(items []Item) {
for _, item := range items {
mu.Lock()
process(item)
mu.Unlock()
}
}
// Or: Extract to function with deferfuncprocessMany(items []Item) {
for _, item := range items {
processOne(item)
}
}
funcprocessOne(item Item) {
mu.Lock()
defer mu.Unlock()
process(item)
}
Quick Reference
Profiling Commands
# CPU profile
go test -cpuprofile=cpu.prof -bench=.
go tool pprof -http=:8080 cpu.prof
# Memory profile
go test -memprofile=mem.prof -bench=.
go tool pprof -http=:8080 mem.prof
# HTTP profiling (production)
curl http://localhost:6060/debug/pprof/profile?seconds=30 > cpu.prof
Benchmarking Commands
# Run benchmarks with memory stats
go test -bench=. -benchmem
# Compare before/after
go test -bench=. -count=10 > old.txt
benchstat old.txt new.txt