Benchmarking
Benchmarking measures code performance. Go's testing package includes built-in benchmarking tools. In this lesson, you'll learn to write benchmarks, analyze results, and optimize your code.
Writing Benchmarks
// fibonacci.go
package fib
func Fibonacci(n int) int {
if n <= 1 {
return n
}
return Fibonacci(n-1) + Fibonacci(n-2)
}
// fibonacci_test.go
package fib
import "testing"
func BenchmarkFibonacci(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(10)
}
}
func BenchmarkFibonacci20(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(20)
}
}
- Function name starts with
Benchmark - Takes
*testing.Bparameter - Loop
b.Ntimes (framework sets N) - File name ends with
_test.go
Running Benchmarks
# Run all benchmarks
go test -bench=.
# Run specific benchmark
go test -bench=BenchmarkFibonacci
# Run with memory stats
go test -bench=. -benchmem
# Run for longer (more accurate)
go test -bench=. -benchtime=10s
Understanding Benchmark Output
BenchmarkFibonacci-8 3000000 450 ns/op 0 B/op 0 allocs/op
BenchmarkFibonacci20-8 5000 250000 ns/op 0 B/op 0 allocs/op
| Column | Meaning |
|---|---|
BenchmarkFibonacci-8 |
Benchmark name, 8 = GOMAXPROCS |
3000000 |
Number of iterations (b.N) |
450 ns/op |
Time per operation |
0 B/op |
Bytes allocated per operation |
0 allocs/op |
Allocations per operation |
Benchmark Patterns
Table-Driven Benchmarks
func BenchmarkFibonacci(b *testing.B) {
benchmarks := []struct {
name string
n int
}{
{"Fib10", 10},
{"Fib20", 20},
{"Fib30", 30},
}
for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(bm.n)
}
})
}
}
Resetting Timer
func BenchmarkWithSetup(b *testing.B) {
// Expensive setup
data := generateLargeDataset()
b.ResetTimer() // Don't count setup time
for i := 0; i < b.N; i++ {
process(data)
}
}
Stopping and Starting Timer
func BenchmarkWithPauses(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
// Expensive setup per iteration
data := setup()
b.StartTimer()
// Actual code to benchmark
process(data)
}
}
Comparing Performance
Example: String Concatenation
func BenchmarkStringConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
s := ""
for j := 0; j < 100; j++ {
s += "x"
}
}
}
func BenchmarkStringBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for j := 0; j < 100; j++ {
sb.WriteString("x")
}
_ = sb.String()
}
}
Using benchstat
# Install benchstat
go install golang.org/x/perf/cmd/benchstat@latest
# Run benchmark before optimization
go test -bench=. -count=10 > old.txt
# Make changes, run again
go test -bench=. -count=10 > new.txt
# Compare
benchstat old.txt new.txt
Profiling
CPU Profiling
# Generate CPU profile
go test -bench=. -cpuprofile=cpu.prof
# Analyze with pprof
go tool pprof cpu.prof
# Commands in pprof:
# top - Show top functions
# list FunctionName - Show source
# web - Open in browser (requires graphviz)
Memory Profiling
# Generate memory profile
go test -bench=. -memprofile=mem.prof
# Analyze
go tool pprof mem.prof
In-Code Profiling
import (
"os"
"runtime/pprof"
)
func main() {
// CPU profiling
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Your code here
// Memory profiling
mf, _ := os.Create("mem.prof")
pprof.WriteHeapProfile(mf)
mf.Close()
}
Optimization Tips
- Measure first - Don't guess, benchmark!
- Optimize hot paths - Focus on frequently called code
- Avoid premature optimization - Clarity first, speed second
- Use profiling - Find real bottlenecks
- Benchmark changes - Verify improvements
Common Optimizations
// 1. Reduce allocations
// ❌ Slow - allocates every time
func slow() []int {
return []int{1, 2, 3}
}
// ✅ Fast - reuse slice
var pool = []int{1, 2, 3}
func fast() []int {
return pool
}
// 2. Use sync.Pool for temporary objects
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func process() {
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
buf.Reset()
// Use buf
}
// 3. Preallocate slices
// ❌ Slow - grows dynamically
s := []int{}
for i := 0; i < 1000; i++ {
s = append(s, i)
}
// ✅ Fast - preallocated
s := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
s = append(s, i)
}
Common Mistakes
1. Not running enough iterations
// ❌ Wrong - fixed iterations
func BenchmarkWrong(b *testing.B) {
for i := 0; i < 100; i++ {
doWork()
}
}
// ✅ Correct - use b.N
func BenchmarkCorrect(b *testing.B) {
for i := 0; i < b.N; i++ {
doWork()
}
}
2. Including setup in benchmark
// ❌ Wrong - setup counted
func BenchmarkWrong(b *testing.B) {
for i := 0; i < b.N; i++ {
data := setup() // Counted!
process(data)
}
}
// ✅ Correct - reset timer
func BenchmarkCorrect(b *testing.B) {
data := setup()
b.ResetTimer()
for i := 0; i < b.N; i++ {
process(data)
}
}
Exercise: Optimize String Building
Task: Benchmark and optimize string concatenation.
Requirements:
- Benchmark 3 methods: +=, strings.Builder, bytes.Buffer
- Test with 100 and 1000 iterations
- Include memory stats
- Identify the fastest method
Show Solution
package stringbench
import (
"bytes"
"strings"
"testing"
)
func BenchmarkStringConcat(b *testing.B) {
benchmarks := []struct {
name string
n int
}{
{"100", 100},
{"1000", 1000},
}
for _, bm := range benchmarks {
b.Run("Plus_"+bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
s := ""
for j := 0; j < bm.n; j++ {
s += "x"
}
}
})
b.Run("Builder_"+bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for j := 0; j < bm.n; j++ {
sb.WriteString("x")
}
_ = sb.String()
}
})
b.Run("Buffer_"+bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
var buf bytes.Buffer
for j := 0; j < bm.n; j++ {
buf.WriteString("x")
}
_ = buf.String()
}
})
}
}
// Run with: go test -bench=. -benchmem
Summary
- Benchmarks start with
Benchmark - Loop b.N times for accurate results
- go test -bench=. runs benchmarks
- -benchmem shows memory stats
- b.ResetTimer() excludes setup time
- benchstat compares results
- pprof profiles CPU and memory
- Measure before optimizing
What's Next?
You've learned to measure and optimize performance! Now you're ready for the final lesson: Best Practices. You'll learn Go idioms, code organization, and professional development practices to write production-ready code!
Enjoying these tutorials?