CODE: Google Benchmark — Micro-Benchmarking for C++

Google Benchmark is the standard C++ micro-benchmarking framework. It provides a statistically rigorous way to measure the performance of specific functions, compare implementation alternatives, and validate optimization decisions. Understanding how to write and interpret benchmarks is essential for any performance-critical C++ work.

In high-performance C++ development, intuition about performance is frequently wrong. Function A looks more efficient than Function B — but the branch predictor, cache behavior, or compiler optimizer may tell a completely different story. Google Benchmark provides the discipline: measure first, then optimize, then measure again.

Google Benchmark is a micro-benchmarking framework that runs your code in a controlled environment, repeating it many times to gather statistically meaningful performance data. It reports mean execution time, standard deviation, and throughput, making it the ideal tool for comparing implementations and validating optimizations.


What Is Google Benchmark?

Google Benchmark is a C++ library for writing, running, and analyzing micro-benchmarks. It is developed and maintained by Google and is available at https://github.com/google/benchmark.

Key features:

  • Statistically reliable measurements (runs benchmarks until stable)
  • Automatic time and iteration count management
  • Support for parameterized benchmarks (test across multiple input sizes)
  • CPU and wall-clock time measurement
  • Output in multiple formats (console, JSON, CSV)
  • Prevention of dead-code elimination with DoNotOptimize
  • Integration with Google Test for combined test + benchmark suites

Setting Up Google Benchmark

With CMake FetchContent

cmake
cmake_minimum_required(VERSION 3.14)
project(MyBenchmarks CXX)

include(FetchContent)
FetchContent_Declare(
    googlebenchmark
    URL https://github.com/google/benchmark/archive/v1.8.3.zip)

set(BENCHMARK_ENABLE_TESTING OFF)  # disable benchmark's own tests
FetchContent_MakeAvailable(googlebenchmark)

add_executable(my_benchmarks benchmarks/bench_mylib.cpp)
target_link_libraries(my_benchmarks PRIVATE benchmark::benchmark_main)

Including the Header

cpp
#include <benchmark/benchmark.h>


Writing Your First Benchmark

The basic structure of a Google Benchmark:

cpp
#include <benchmark/benchmark.h>

// The benchmark function takes a benchmark::State& parameter
static void BM_MyFunction(benchmark::State& state) {
    // Setup code (not timed)
    int n = state.range(0);

    for (auto _ : state) {
        // This loop body IS timed
        // Put the code you want to measure here
        int result = some_expensive_function(n);
        benchmark::DoNotOptimize(result); // prevent dead-code elimination
    }
}

// Register the benchmark
BENCHMARK(BM_MyFunction)->Arg(10)->Arg(100)->Arg(1000);

// The main function is provided by benchmark_main
BENCHMARK_MAIN();


A Complete Example: Fibonacci

cpp
#include <benchmark/benchmark.h>

// Recursive Fibonacci (exponential time)
int FibonacciRecursive(int n) {
    if (n <= 1) return n;
    return FibonacciRecursive(n - 1) + FibonacciRecursive(n - 2);
}

// Iterative Fibonacci (linear time)
int FibonacciIterative(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1;
    for (int i = 2; i <= n; ++i) {
        int c = a + b;
        a = b;
        b = c;
    }
    return b;
}

// Benchmark for recursive
static void BM_FibonacciRecursive(benchmark::State& state) {
    int n = state.range(0);
    for (auto _ : state) {
        int result = FibonacciRecursive(n);
        benchmark::DoNotOptimize(result);
    }
}

// Benchmark for iterative
static void BM_FibonacciIterative(benchmark::State& state) {
    int n = state.range(0);
    for (auto _ : state) {
        int result = FibonacciIterative(n);
        benchmark::DoNotOptimize(result);
    }
}

// Register both with the same arguments for direct comparison
BENCHMARK(BM_FibonacciRecursive)->Arg(10)->Arg(20)->Arg(30);
BENCHMARK(BM_FibonacciIterative)->Arg(10)->Arg(20)->Arg(30);

BENCHMARK_MAIN();

Sample output:

text
-----------------------------------------------------------------------
Benchmark                          Time             CPU   Iterations
-----------------------------------------------------------------------
BM_FibonacciRecursive/10         514 ns          513 ns      1362042
BM_FibonacciRecursive/20      172514 ns       172340 ns         4062
BM_FibonacciRecursive/30    21534811 ns     21526500 ns           32
BM_FibonacciIterative/10         2.1 ns          2.1 ns    332581832
BM_FibonacciIterative/20         3.9 ns          3.9 ns    180034521
BM_FibonacciIterative/30         5.7 ns          5.7 ns    122890431

The iterative version is approximately 4,000,000x faster for n=30. This is exactly the kind of insight benchmarking provides.


Understanding the Benchmark Loop

The for (auto _ : state) loop is managed by the benchmark framework:

  1. On the first few iterations, the framework measures wall time
  2. It adjusts the number of iterations to run the benchmark for a statistically meaningful duration (usually ~1 second)
  3. It reports the average time per iteration and total iterations

cpp
for (auto _ : state) {
    // ONLY this code is measured
    // Everything inside the loop body is timed
    int result = my_function(n);
    benchmark::DoNotOptimize(result);
}


`DoNotOptimize` and `ClobberMemory`

A critical concern in benchmarking: the compiler may optimize away your benchmark code if it can prove the result is unused. Google Benchmark provides two helpers:

cpp
// DoNotOptimize: tells the compiler the value might be read
// Prevents dead-code elimination
benchmark::DoNotOptimize(result);

// ClobberMemory: tells the compiler that memory state has changed
// Prevents the compiler from hoisting loads out of the loop
benchmark::ClobberMemory();

cpp
static void BM_VectorSum(benchmark::State& state) {
    std::vector<int> v(state.range(0), 1);

    for (auto _ : state) {
        int sum = 0;
        for (int x : v) sum += x;
        benchmark::DoNotOptimize(sum);  // prevent optimizing away the sum
    }
}


Parameterized Benchmarks

The real power of Google Benchmark is measuring how performance scales with input size:

cpp
// Single argument
BENCHMARK(BM_MyFunction)->Arg(8)->Arg(64)->Arg(512)->Arg(4096);

// Range: powers of 2 from 1 to 1024
BENCHMARK(BM_MyFunction)->Range(1, 1024);

// Multiple arguments (e.g., for 2D problems)
BENCHMARK(BM_MatrixMultiply)->Args({8, 8})->Args({64, 64})->Args({256, 256});

// Dense range
BENCHMARK(BM_MyFunction)->DenseRange(1, 10, 1);  // 1, 2, 3, ..., 10

// Parametric sweep with RangeMultiplier
BENCHMARK(BM_MyFunction)->RangeMultiplier(2)->Range(1, 1 << 20);
// Tests: 1, 2, 4, 8, ..., 1048576


Setup and Teardown

Code outside the benchmark loop is not timed. Use this for expensive setup:

cpp
static void BM_SortVector(benchmark::State& state) {
    // Not timed: setup
    std::vector<int> data(state.range(0));
    std::iota(data.begin(), data.end(), 0);  // fill with 0, 1, 2, ...

    for (auto _ : state) {
        // Pause timing while we re-shuffle
        state.PauseTiming();
        std::shuffle(data.begin(), data.end(), std::mt19937{});
        state.ResumeTiming();

        // Timed: the sort
        std::sort(data.begin(), data.end());
        benchmark::DoNotOptimize(data.data());
    }
}

BENCHMARK(BM_SortVector)->RangeMultiplier(4)->Range(64, 1 << 20);


Comparing Implementations

A common use case: comparing two implementations of the same operation:

cpp
// Method 1: using std::accumulate
static void BM_Accumulate(benchmark::State& state) {
    std::vector<double> v(state.range(0), 1.0);
    for (auto _ : state) {
        double sum = std::accumulate(v.begin(), v.end(), 0.0);
        benchmark::DoNotOptimize(sum);
    }
}

// Method 2: raw loop
static void BM_RawLoop(benchmark::State& state) {
    std::vector<double> v(state.range(0), 1.0);
    for (auto _ : state) {
        double sum = 0.0;
        for (double x : v) sum += x;
        benchmark::DoNotOptimize(sum);
    }
}

BENCHMARK(BM_Accumulate)->RangeMultiplier(4)->Range(64, 1 << 20);
BENCHMARK(BM_RawLoop)->RangeMultiplier(4)->Range(64, 1 << 20);


Reporting Throughput

For operations where throughput matters more than latency:

cpp
static void BM_MemoryCopy(benchmark::State& state) {
    std::vector<char> src(state.range(0), 'A');
    std::vector<char> dst(state.range(0));

    for (auto _ : state) {
        std::copy(src.begin(), src.end(), dst.begin());
        benchmark::ClobberMemory();
    }

    // Report bytes per second
    state.SetBytesProcessed(
        static_cast<int64_t>(state.iterations()) *
        static_cast<int64_t>(state.range(0)));
}

BENCHMARK(BM_MemoryCopy)->RangeMultiplier(4)->Range(64, 1 << 24);

Output includes GB/s alongside time metrics.


Running Benchmarks

bash
# Build and run
./my_benchmarks

# Run only benchmarks matching a filter
./my_benchmarks --benchmark_filter=BM_Fibonacci

# Output to JSON for further analysis
./my_benchmarks --benchmark_out=results.json --benchmark_out_format=json

# Repeat each benchmark for more stable results
./my_benchmarks --benchmark_repetitions=5

# Minimum time per benchmark
./my_benchmarks --benchmark_min_time=2s


The Benchmark Workflow

text
Performance Optimization Workflow:
+---------------------------+
| 1. Write tests (GTest)    |  Establish correctness baseline
+---------------------------+
           |
           v
+---------------------------+
| 2. Write benchmarks       |  Measure current performance
+---------------------------+
           |
           v
+---------------------------+
| 3. Profile                |  Identify the actual bottleneck
+---------------------------+
           |
           v
+---------------------------+
| 4. Optimize               |  Apply targeted improvement
+---------------------------+
           |
           v
+---------------------------+
| 5. Run benchmarks         |  Verify improvement
+---------------------------+
           |
           v
+---------------------------+
| 6. Run tests              |  Verify correctness preserved
+---------------------------+

Never skip steps 1 and 6. An optimization that breaks correctness is not an optimization — it is a bug.


Conclusion

Google Benchmark eliminates guesswork from C++ performance optimization. By providing statistically reliable measurements in a controlled environment, it gives you the data you need to make informed decisions: which implementation is faster, by how much, and at what input sizes.

Combined with Google Test for correctness verification, Google Benchmark completes the essential toolkit for high-performance C++ development. Measure everything. Trust the measurements. Optimize what matters.