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_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
#include <benchmark/benchmark.h>
Writing Your First Benchmark
The basic structure of a Google Benchmark:
#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
#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:
-----------------------------------------------------------------------
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:
- On the first few iterations, the framework measures wall time
- It adjusts the number of iterations to run the benchmark for a statistically meaningful duration (usually ~1 second)
- It reports the average time per iteration and total iterations
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:
// 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();
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:
// 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:
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:
// 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:
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
# 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
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.