Your first Go benchmark will run 100 million times.

You wrote a function that works. Now you want to know if it’s fast enough. Go’s built-in benchmarking runs your code millions of times and tells you exactly how many nanoseconds each operation takes.

No external tools. No frameworks to install. Just add Benchmark to your test file and run one command.

Your first benchmark (right next to your tests)

You already know how to write tests. Benchmarks use the same file, same package, similar structure.

hello_test.go

package main

import "testing"

func Hello(name string) string {
    return "Hello, " + name
}

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Hello("World")
    }
}

Three things to notice:

  1. Benchmark functions start with Benchmark
  2. They take *testing.B instead of *testing.T
  3. They run your code b.N times in a loop

Run go test -bench=.:

BenchmarkHello-8    50000000    24.3 ns/op
PASS

Your function ran 50 million times. Each call took 24.3 nanoseconds. Go figured out how many times to run it automatically.

Why it runs millions of times

Your function is fast. To measure something that takes 24 nanoseconds, Go needs to run it many times to get an accurate average. Fast functions run millions of times. Slower functions run fewer times.

You don’t control b.N. Go adjusts it until the benchmark runs for about 1 second total. That’s how it gets reliable measurements.

Compare two approaches

You can write multiple benchmarks to compare different ways of doing the same thing. Here’s string concatenation two different ways:

func HelloWithPlus(name string) string {
    return "Hello, " + name
}

func HelloWithSprintf(name string) string {
    return fmt.Sprintf("Hello, %s", name)
}

func BenchmarkHelloWithPlus(b *testing.B) {
    for i := 0; i < b.N; i++ {
        HelloWithPlus("World")
    }
}

func BenchmarkHelloWithSprintf(b *testing.B) {
    for i := 0; i < b.N; i++ {
        HelloWithSprintf("World")
    }
}

Run go test -bench=.:

BenchmarkHelloWithPlus-8        50000000    24.3 ns/op
BenchmarkHelloWithSprintf-8     10000000    112 ns/op
PASS

The + operator is about 5x faster than fmt.Sprintf. You learned this by measuring, not guessing.

What those numbers mean

BenchmarkHello-8    50000000    24.3 ns/op

Breaking this down:

The important number is ns/op. Lower is faster.

See memory usage

Add -benchmem to see how much memory your code allocates:

go test -bench=. -benchmem
BenchmarkHelloWithPlus-8        50000000    24.3 ns/op    13 B/op    1 allocs/op
BenchmarkHelloWithSprintf-8     10000000    112 ns/op     21 B/op    2 allocs/op
PASS

New columns:

Fewer allocations usually means less work for the garbage collector.

Commands you’ll use

go test -bench=.              # Run all benchmarks
go test -bench=Hello          # Run benchmarks matching "Hello"
go test -bench=. -benchmem    # Include memory statistics

When to write benchmarks

You don’t need to benchmark everything. Write benchmarks when:

Don’t guess about performance. Measure it.

What you’ll learn

After writing a few benchmarks, you’ll start noticing patterns. String concatenation with + is faster than fmt.Sprintf for simple cases. Creating maps has overhead. Some things you think are slow are actually fast, and vice versa.

Benchmarks teach you which Go patterns are fast and which are slow. You learn by measuring your actual code, not memorizing rules.

Start with a function you’ve already written. Add a benchmark. Run it. See what you learn. The numbers might surprise you.