Writing Benchmarks in Go
Go's testing package includes built-in support for benchmarking code performance. This guide covers how to write, run, and analyze benchmarks effectively.
Basic Benchmarks
Writing Your First Benchmark
func BenchmarkExample(b *testing.B) { // Reset timer before main benchmark loop b.ResetTimer() // Run the target code b.N times for i := 0; i < b.N; i++ { Calculate(100) } } Running Benchmarks
# Run all benchmarks go test -bench=. # Run specific benchmark go test -bench=BenchmarkExample # Run benchmarks with more iterations go test -bench=. -benchtime=5s # Run benchmarks with memory allocation stats go test -bench=. -benchmem Advanced Benchmarking
Benchmarking with Setup
func BenchmarkComplexOperation(b *testing.B) { // Setup code data := make([]int, 1000) for i := range data { data[i] = rand.Intn(1000) } // Reset timer after setup b.ResetTimer() for i := 0; i < b.N; i++ { ProcessData(data) } } Sub-benchmarks
func BenchmarkProcessing(b *testing.B) { sizes := []int{100, 1000, 10000} for _, size := range sizes { b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) { data := make([]int, size) for i := range data { data[i] = rand.Intn(size) } b.ResetTimer() for i := 0; i < b.N; i++ { ProcessData(data) } }) } } Memory Benchmarks
Measuring Allocations
func BenchmarkMemoryUsage(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { data := make([]int, 1000) ProcessData(data) } } Comparing Implementations
func BenchmarkImplementation(b *testing.B) { implementations := map[string]func([]int) int{ "simple": SimpleSum, "optimized": OptimizedSum, } data := make([]int, 1000) for i := range data { data[i] = rand.Intn(100) } for name, impl := range implementations { b.Run(name, func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { impl(data) } }) } } Parallel Benchmarks
Testing Concurrent Code
func BenchmarkParallel(b *testing.B) { // Run parallel benchmark b.RunParallel(func(pb *testing.PB) { for pb.Next() { ProcessData(100) } }) } Controlling Parallelism
func BenchmarkWithParallelism(b *testing.B) { // Set number of parallel routines b.SetParallelism(4) b.RunParallel(func(pb *testing.PB) { // Local setup per parallel routine local := NewLocalState() for pb.Next() { local.Process() } }) } Best Practices
1. Reset Timer Appropriately
func BenchmarkWithSetup(b *testing.B) { // Setup phase heavySetup() // Reset timer before the operation we want to measure b.ResetTimer() for i := 0; i < b.N; i++ { Operation() } } 2. Prevent Compiler Optimizations
func BenchmarkCompute(b *testing.B) { var result int for i := 0; i < b.N; i++ { result = Compute(100) } // Prevent compiler from optimizing away the computation if result > 0 { b.Log("Positive result") } } 3. Use Realistic Data
func BenchmarkRealWorld(b *testing.B) { // Load real-world test data data := loadTestData() b.ResetTimer() for i := 0; i < b.N; i++ { ProcessRealData(data) } } 4. Profile Memory Usage
func BenchmarkMemory(b *testing.B) { b.ReportAllocs() // Record initial memory stats var m runtime.MemStats runtime.ReadMemStats(&m) initialAlloc := m.TotalAlloc for i := 0; i < b.N; i++ { ProcessWithMemory() } // Record final memory stats runtime.ReadMemStats(&m) b.Logf("Memory used: %d bytes", m.TotalAlloc-initialAlloc) } Analysis Tools
Using pprof
import "runtime/pprof" func BenchmarkWithProfile(b *testing.B) { // CPU profiling if cpuProfile, err := os.Create("cpu.prof"); err == nil { pprof.StartCPUProfile(cpuProfile) defer pprof.StopCPUProfile() } // Memory profiling if memProfile, err := os.Create("mem.prof"); err == nil { defer func() { runtime.GC() pprof.WriteHeapProfile(memProfile) memProfile.Close() }() } for i := 0; i < b.N; i++ { ComplexOperation() } } Analyzing Results
# Generate CPU profile go test -bench=. -cpuprofile=cpu.prof # Generate memory profile go test -bench=. -memprofile=mem.prof # Analyze with pprof go tool pprof cpu.prof go tool pprof mem.prof # Generate profile graph go tool pprof -png cpu.prof > cpu.png Common Patterns
Benchmark State
type BenchmarkState struct { data []int config *Config cleanup func() } func setupBenchmark(b *testing.B) *BenchmarkState { b.Helper() state := &BenchmarkState{ data: generateData(1000), config: NewConfig(), } state.cleanup = func() { // Cleanup resources } return state } func BenchmarkWithState(b *testing.B) { state := setupBenchmark(b) defer state.cleanup() b.ResetTimer() for i := 0; i < b.N; i++ { ProcessWithState(state.data, state.config) } } Next Steps
- Learn about Test Coverage
- Explore Mocking
- Study HTTP Testing