Benchmarking in Go
Benchmarking helps you measure the performance of your Go code. Go’s testing package includes built-in support for benchmarks, making it easy to compare different implementations and spot performance issues.
Basic Benchmark
To write a benchmark, create a function that starts with Benchmark and takes a *testing.B parameter. The testing framework will run your code multiple times to get accurate measurements.
package main
import "testing"
func Fibonacci(n int) int {
if n <= 1 {
return n
}
return Fibonacci(n-1) + Fibonacci(n-2)
}
func BenchmarkFibonacci(b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(20)
}
}Run benchmarks with go test -bench=. or go test -bench=BenchmarkFibonacci. You’ll see output like:
BenchmarkFibonacci-8 100000 15296 ns/opThis means the benchmark ran 100000 times, with each operation taking about 15296 nanoseconds.
Comparing Implementations
Benchmarks are great for comparing different ways to solve the same problem:
package main
import "testing"
// Recursive implementation
func FibonacciRecursive(n int) int {
if n <= 1 {
return n
}
return FibonacciRecursive(n-1) + FibonacciRecursive(n-2)
}
// Iterative implementation
func FibonacciIterative(n int) int {
if n <= 1 {
return n
}
a, b := 0, 1
for i := 2; i <= n; i++ {
a, b = b, a+b
}
return b
}
func BenchmarkFibonacciRecursive(b *testing.B) {
for i := 0; i < b.N; i++ {
FibonacciRecursive(20)
}
}
func BenchmarkFibonacciIterative(b *testing.B) {
for i := 0; i < b.N; i++ {
FibonacciIterative(20)
}
}Running this will show which implementation is faster.
Benchmarking with Setup
Sometimes you need to set up data before benchmarking:
package main
import (
"math/rand"
"sort"
"testing"
)
func BenchmarkSort(b *testing.B) {
// Setup: create a slice of random numbers
data := make([]int, 1000)
for i := range data {
data[i] = rand.Int()
}
b.ResetTimer() // Reset timer to exclude setup time
for i := 0; i < b.N; i++ {
// Make a copy to avoid modifying the original
copy := make([]int, len(data))
copy(copy, data)
sort.Ints(copy)
}
}Use b.ResetTimer() to exclude setup time from the measurement.
Sub-Benchmarks
For testing different variations of the same operation:
package main
import (
"strings"
"testing"
)
func BenchmarkStringConcat(b *testing.B) {
s1 := "Hello"
s2 := "World"
b.Run("plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = s1 + s2
}
})
b.Run("sprintf", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("%s%s", s1, s2)
}
})
b.Run("builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var builder strings.Builder
builder.WriteString(s1)
builder.WriteString(s2)
_ = builder.String()
}
})
}This will run separate benchmarks for each concatenation method.
Memory Benchmarks
To measure memory allocations:
package main
import "testing"
func BenchmarkSliceAppend(b *testing.B) {
b.ReportAllocs()
var s []int
for i := 0; i < b.N; i++ {
s = append(s, i)
}
}b.ReportAllocs() includes memory allocation statistics in the output.
Parallel Benchmarks
To test concurrent performance:
package main
import "testing"
func BenchmarkParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
// Do some work
_ = FibonacciIterative(10)
}
})
}This runs the benchmark in parallel across multiple goroutines.
Running Benchmarks
go test -bench=.: Run all benchmarksgo test -bench=BenchmarkName: Run specific benchmarkgo test -bench=. -benchmem: Include memory allocation infogo test -bench=. -count=5: Run benchmarks 5 times for more accurate resultsgo test -bench=. -benchtime=10s: Run benchmarks for at least 10 seconds
Interpreting Results
- ns/op: Nanoseconds per operation - lower is better
- B/op: Bytes allocated per operation
- allocs/op: Number of allocations per operation
When comparing benchmarks, look for significant differences (usually 10% or more).
Common Benchmarking Mistakes
- Including setup time: Use
b.ResetTimer()to exclude setup - Not resetting state: Make sure each iteration starts with clean state
- Testing too small operations: Use
b.Nto run enough iterations - Ignoring compiler optimizations: Benchmarks can be affected by inlining, etc.
- Not using realistic data: Test with data that matches real usage
Profiling with Benchmarks
You can use benchmarks to trigger profiling:
go test -bench=. -cpuprofile=cpu.prof
go tool pprof cpu.profThis helps identify performance bottlenecks.
Remember, benchmarks help you understand performance characteristics, but always profile your actual application for the best insights.
For more on profiling, see our profiling tutorial. If you need to learn about testing basics first, check out unit testing.