Loading...
Loading...

Go Benchmarking Tutorial

Benchmarking in Go helps measure and optimize code performance. The testing package provides powerful benchmarking tools that integrate seamlessly with your test suite. This tutorial covers everything from basic benchmarks to advanced techniques.

1. Benchmarking Basics

1.1 Benchmark Structure

Go benchmarks follow specific conventions:

  • Benchmark functions start with Benchmark
  • Take a single *testing.B parameter
  • Contain a loop running b.N times
// sum.go
func Sum(numbers []int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

// sum_test.go
func BenchmarkSum(b *testing.B) {
    numbers := []int{1, 2, 3, 4, 5}
    for i := 0; i < b.N; i++ {
        Sum(numbers)
    }
}

1.2 Running Benchmarks

Execute benchmarks with specific flags:

# Run all benchmarks
go test -bench=.

# Run specific benchmark
go test -bench=BenchmarkSum

# Run with memory allocation stats
go test -bench=. -benchmem

# Run with CPU profiling
go test -bench=. -cpuprofile=cpu.out

2. Benchmark Measurement

2.1 Timing Control

Accurate measurement requires proper timer control:

func BenchmarkExpensiveSetup(b *testing.B) {
    // Setup not measured
    data := make([]int, 10000)
    for i := range data {
        data[i] = i
    }
    
    b.ResetTimer() // Reset timer after setup
    
    for i := 0; i < b.N; i++ {
        process(data) // Only measure this
    }
}

2.2 Memory Allocation Tracking

Measure memory usage with -benchmem:

func BenchmarkAllocations(b *testing.B) {
    b.ReportAllocs() // Explicit allocation reporting
    
    for i := 0; i < b.N; i++ {
        // Slice creation causes allocations
        data := make([]int, 100)
        _ = data
    }
}

3. Advanced Benchmark Patterns

3.1 Sub-benchmarks

Group related benchmarks with different inputs:

func BenchmarkProcess(b *testing.B) {
    benchmarks := []struct {
        name string
        size int
    }{
        {"Small", 10},
        {"Medium", 100},
        {"Large", 1000},
    }

    for _, bm := range benchmarks {
        b.Run(bm.name, func(b *testing.B) {
            data := make([]int, bm.size)
            b.ResetTimer()
            
            for i := 0; i < b.N; i++ {
                process(data)
            }
        })
    }
}

3.2 Parallel Benchmarks

Test concurrent performance:

func BenchmarkParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            // Concurrent benchmark code
            process(data)
        }
    })
}

4. Benchmark Comparison

4.1 Comparing Implementations

Test alternative implementations side-by-side:

func BenchmarkOriginal(b *testing.B) {
    data := generateData(1000)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        originalAlgorithm(data)
    }
}

func BenchmarkOptimized(b *testing.B) {
    data := generateData(1000)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        optimizedAlgorithm(data)
    }
}

4.2 Benchmark Statistics

Interpret benchmark output:

BenchmarkSum-8           10000000               120 ns/op               0 B/op          0 allocs/op
BenchmarkParallelSum-8   20000000                65 ns/op               0 B/op          0 allocs/op

Key metrics:

  • 10000000: Number of iterations
  • 120 ns/op: Time per operation
  • 0 B/op: Bytes allocated per operation
  • 0 allocs/op: Memory allocations per operation

5. Benchmark Profiling

5.1 Generating Profiles

Create performance profiles for analysis:

# CPU profile
go test -bench=. -cpuprofile=cpu.pprof

# Memory profile
go test -bench=. -memprofile=mem.pprof

# Block profile
go test -bench=. -blockprofile=block.pprof

5.2 Analyzing Profiles

Use pprof to analyze performance:

# Interactive terminal mode
go tool pprof cpu.pprof

# Web UI visualization
go tool pprof -http=:8080 cpu.pprof

# Top CPU consumers
(pprof) top10

# Generate flamegraph
go tool pprof -http=:8080 -no_browser -tags= -nodefraction=0 -edgefraction=0 -nodecount=100000 cpu.pprof

6. Benchmark Best Practices

6.1 Benchmark Design Principles

  • Isolate the code under test: Minimize setup in measurement loop
  • Use realistic data: Test with production-like inputs
  • Run benchmarks multiple times: Use count flag to detect variance
  • Compare fairly: Ensure all benchmarks use same input data

6.2 Common Pitfalls

// ❌ Bad: Including setup in measurement
func BenchmarkBad(b *testing.B) {
    for i := 0; i < b.N; i++ {
        data := makeLargeDataset() // Expensive setup in loop
        process(data)
    }
}

// ✅ Good: Proper timer control
func BenchmarkGood(b *testing.B) {
    data := makeLargeDataset() // Setup outside loop
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        process(data)
    }
}

// ❌ Bad: Not enough iterations
func BenchmarkShort(b *testing.B) {
    for i := 0; i < 10; i++ { // Fixed small number
        process(data)
    }
}

// ✅ Good: Let testing.B determine iterations
func BenchmarkProper(b *testing.B) {
    for i := 0; i < b.N; i++ {
        process(data)
    }
}

7. Real-world Benchmark Examples

7.1 String Concatenation

Compare different string building methods:

func BenchmarkConcatPlus(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var s string
        for j := 0; j < 100; j++ {
            s += "a"
        }
        _ = s
    }
}

func BenchmarkConcatBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var builder strings.Builder
        for j := 0; j < 100; j++ {
            builder.WriteString("a")
        }
        _ = builder.String()
    }
}

7.2 JSON Encoding

Benchmark different encoding approaches:

type User struct {
    ID    int
    Name  string
    Email string
}

func BenchmarkJSONMarshal(b *testing.B) {
    user := User{1, "Alice", "alice@example.com"}
    for i := 0; i < b.N; i++ {
        _, err := json.Marshal(user)
        if err != nil {
            b.Fatal(err)
        }
    }
}

func BenchmarkJSONEncoder(b *testing.B) {
    user := User{1, "Alice", "alice@example.com"}
    for i := 0; i < b.N; i++ {
        var buf bytes.Buffer
        encoder := json.NewEncoder(&buf)
        if err := encoder.Encode(user); err != nil {
            b.Fatal(err)
        }
    }
}
0 Interaction
0 Views
Views
0 Likes
×
×
×
🍪 CookieConsent@Ptutorials:~

Welcome to Ptutorials

$ Allow cookies on this site ? (y/n)

top-home