Benchmarking Go Code
Benchmarking is essential for understanding and optimizing Go program performance. This guide covers writing benchmarks, analyzing results, and identifying bottlenecks.
Basic Benchmarks
Simple Benchmark
package main
import "testing"
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
func Add(a, b int) int {
return a + b
}
Running Benchmarks
# Run all benchmarks
go test -bench=.
# Run specific benchmark
go test -bench=BenchmarkAdd
# Show memory allocations
go test -bench=. -benchmem
# Run for specific duration
go test -bench=. -benchtime=10s
# Run with CPU count
go test -bench=. -cpu=1,2,4,8
# Save results
go test -bench=. -benchmem > results.txt
Benchmark Output
Understanding Results
BenchmarkAdd-8 1000000000 1.23 ns/op 0 B/op 0 allocs/op
BenchmarkAdd-8: Benchmark name and GOMAXPROCS1000000000: Number of iterations (b.N)1.23 ns/op: Nanoseconds per operation0 B/op: Bytes allocated per operation0 allocs/op: Number of allocations per operation
Benchmark Patterns
Benchmarking Different Inputs
package main
import "testing"
func BenchmarkAddSmall(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
func BenchmarkAddLarge(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1000000, 2000000)
}
}
func Add(a, b int) int {
return a + b
}
Benchmarking with Setup
package main
import (
"testing"
)
func BenchmarkProcessWithSetup(b *testing.B) {
// Setup (not counted)
data := make([]int, 1000)
for i := 0; i < len(data); i++ {
data[i] = i
}
// Reset timer to exclude setup
b.ResetTimer()
// Benchmark (counted)
for i := 0; i < b.N; i++ {
processData(data)
}
}
func processData(data []int) int {
sum := 0
for _, v := range data {
sum += v
}
return sum
}
Benchmarking with Cleanup
package main
import "testing"
func BenchmarkWithCleanup(b *testing.B) {
for i := 0; i < b.N; i++ {
// Setup
resource := allocateResource()
// Benchmark
b.StartTimer()
useResource(resource)
b.StopTimer()
// Cleanup
freeResource(resource)
}
}
func allocateResource() *Resource {
return &Resource{}
}
func useResource(r *Resource) {
// Use resource
}
func freeResource(r *Resource) {
// Free resource
}
type Resource struct{}
Comparing Implementations
Benchmark Multiple Implementations
package main
import (
"testing"
)
// Implementation 1: Using loop
func SumLoop(data []int) int {
sum := 0
for _, v := range data {
sum += v
}
return sum
}
// Implementation 2: Using recursion
func SumRecursive(data []int) int {
if len(data) == 0 {
return 0
}
return data[0] + SumRecursive(data[1:])
}
func BenchmarkSumLoop(b *testing.B) {
data := make([]int, 1000)
for i := 0; i < len(data); i++ {
data[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SumLoop(data)
}
}
func BenchmarkSumRecursive(b *testing.B) {
data := make([]int, 100) // Smaller to avoid stack overflow
for i := 0; i < len(data); i++ {
data[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
SumRecursive(data)
}
}
Memory Benchmarking
Measuring Allocations
package main
import "testing"
func BenchmarkStringConcatenation(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
s := ""
for j := 0; j < 100; j++ {
s += "x"
}
}
}
func BenchmarkStringBuilder(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var sb strings.Builder
for j := 0; j < 100; j++ {
sb.WriteString("x")
}
_ = sb.String()
}
}
Analyzing Memory Usage
# Run with memory stats
go test -bench=. -benchmem
# Output shows:
# BenchmarkStringConcatenation-8 1000 1234567 ns/op 5120 B/op 100 allocs/op
# BenchmarkStringBuilder-8 5000 234567 ns/op 512 B/op 1 allocs/op
Profiling
CPU Profiling
package main
import (
"os"
"runtime/pprof"
"testing"
)
func BenchmarkWithCPUProfile(b *testing.B) {
f, _ := os.Create("cpu.prof")
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
for i := 0; i < b.N; i++ {
expensiveOperation()
}
}
func expensiveOperation() {
sum := 0
for i := 0; i < 1000000; i++ {
sum += i
}
}
Memory Profiling
package main
import (
"os"
"runtime"
"runtime/pprof"
"testing"
)
func BenchmarkWithMemProfile(b *testing.B) {
f, _ := os.Create("mem.prof")
defer f.Close()
for i := 0; i < b.N; i++ {
allocateMemory()
}
runtime.GC()
pprof.WriteHeapProfile(f)
}
func allocateMemory() {
data := make([]int, 1000000)
_ = data
}
Practical Examples
Comparing String Operations
package main
import (
"fmt"
"strings"
"testing"
)
func BenchmarkStringConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
s := "hello" + " " + "world"
_ = s
}
}
func BenchmarkStringFormat(b *testing.B) {
for i := 0; i < b.N; i++ {
s := fmt.Sprintf("%s %s", "hello", "world")
_ = s
}
}
func BenchmarkStringJoin(b *testing.B) {
for i := 0; i < b.N; i++ {
s := strings.Join([]string{"hello", "world"}, " ")
_ = s
}
}
Comparing Data Structures
package main
import (
"testing"
)
func BenchmarkSliceAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
var s []int
for j := 0; j < 1000; j++ {
s = append(s, j)
}
}
}
func BenchmarkSlicePreallocate(b *testing.B) {
for i := 0; i < b.N; i++ {
s := make([]int, 0, 1000)
for j := 0; j < 1000; j++ {
s = append(s, j)
}
}
}
func BenchmarkMapInsert(b *testing.B) {
for i := 0; i < b.N; i++ {
m := make(map[int]int)
for j := 0; j < 1000; j++ {
m[j] = j
}
}
}
Best Practices
โ Good Practices
- Benchmark realistic scenarios - Use real data
- Exclude setup time - Use ResetTimer()
- Run multiple times - Reduce variance
- Measure allocations - Use -benchmem
- Compare implementations - Side-by-side
- Profile before optimizing - Find bottlenecks
- Document results - Track performance
- Test on target hardware - Match production
โ Anti-Patterns
// โ Bad: Compiler optimizes away benchmark
func BenchmarkBad(b *testing.B) {
for i := 0; i < b.N; i++ {
x := 1 + 1
}
}
// โ
Good: Use result to prevent optimization
func BenchmarkGood(b *testing.B) {
var result int
for i := 0; i < b.N; i++ {
result = 1 + 1
}
_ = result
}
// โ Bad: Including setup in benchmark
func BenchmarkBad(b *testing.B) {
for i := 0; i < b.N; i++ {
data := make([]int, 1000)
processData(data)
}
}
// โ
Good: Exclude setup
func BenchmarkGood(b *testing.B) {
data := make([]int, 1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
processData(data)
}
}
Resources and References
Official Documentation
- testing Package - Benchmark reference
- Benchmarking Guide - Official guide
- pprof Package - Profiling
Recommended Reading
- Go Benchmarking Best Practices - Official guide
- Profiling Go Programs - Blog post
- Go Performance Tips - Community guide
Tools and Resources
- Go Playground - Online Go editor
- pprof - Profiling tool
- benchstat - Compare benchmarks
Summary
Benchmarking in Go:
- Write benchmarks in
*_test.gofiles - Use
b.Nfor iteration count - Exclude setup with ResetTimer()
- Measure allocations with -benchmem
- Compare implementations side-by-side
- Profile to find bottlenecks
- Optimize based on data
- Document performance results
Master benchmarking for optimized Go code.
Comments