1. go
  2. /testing
  3. /benchmarking

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