Go cookbook · Intermediate

Benchmark code with testing.B.Loop

You want to know whether an optimisation helps and how many allocations it makes.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Write benchmarks with for b.Loop() (Go 1.24+), which handles timing and keeps the compiler from optimising the work away. Report allocations, compare variants with sub-benchmarks, and use benchstat for statistics.

Solution

package text

import (
	"strings"
	"testing"
)

func JoinPlus(parts []string) string {
	s := ""
	for _, p := range parts {
		s += p + ","
	}
	return s
}

func JoinBuilder(parts []string) string {
	var b strings.Builder
	for _, p := range parts {
		b.WriteString(p)
		b.WriteByte(',')
	}
	return b.String()
}

func BenchmarkJoin(b *testing.B) {
	parts := strings.Fields(strings.Repeat("tea pot mug kettle ", 50))
	b.Run("plus", func(b *testing.B) {
		b.ReportAllocs()
		for b.Loop() {
			JoinPlus(parts)
		}
	})
	b.Run("builder", func(b *testing.B) {
		b.ReportAllocs()
		for b.Loop() {
			JoinBuilder(parts)
		}
	})
}
go test -bench=Join -benchmem -count=10 ./text > new.txt
go run golang.org/x/perf/cmd/benchstat@latest old.txt new.txt

Pitfalls

  • Using b.N loops with setup inside the loop; with b.Loop() setup before the loop isn't timed.
  • Drawing conclusions from one run on a noisy laptop; use -count and benchstat.
  • Benchmarking unrealistic inputs.

Variations

  • Profile with -cpuprofile cpu.out and go tool pprof cpu.out.
  • Use profile-guided optimisation (default.pgo) with production profiles.

Further reading: testing.B.Loop

Reference: https://pkg.go.dev/testing#B.Loop

More Go recipes

Was this page helpful?