Choosing our Benchmarking Strategy
We are going to usegoogle_benchmark,
the standard C++ benchmarking library maintained by Google. It’s widely adopted
across the C++ ecosystem, supports fixtures and parameterized benchmarks with
statistical analysis, and works with CMake, Bazel, and other build systems.
This guide uses CMake as the build system. If you’re
using Bazel, check out the Bazel integration
documentation for build instructions.
Your First Benchmark
Let’s start by creating a benchmark for a recursive Fibonacci function to see how we can measure computational performance.Project Setup
First, create a basic project structure:Writing the Benchmark
Create a new filebenchmarks/main.cpp:
volatile int n = 30prevents the compiler from computing the result at compile timebenchmark::State& stateprovides the benchmark loop that runs your code multiple timesfor (auto _ : state)is where your actual benchmark code goes - this loop is timedbenchmark::DoNotOptimize()prevents the compiler from optimizing away the resultBENCHMARK()registers your function as a benchmark->Unit(benchmark::kMillisecond)displays results in milliseconds for better readability as by default it’s in nanoseconds
BENCHMARK_MAIN()provides the entry point that discovers and runs all benchmarks
Configuration with CMake
Create aCMakeLists.txt file in the benchmarks/ folder:
benchmarks/CMakeLists.txt
CMAKE_BUILD_TYPE RelWithDebInfoenables optimizations with debug symbols for accurate profiling- We use CodSpeed’s fork of
google_benchmarkwhich adds performance measurement capabilities and CI integration BENCHMARK_DOWNLOAD_DEPENDENCIES ONallows google_benchmark to download its dependencies
Building and Running the Benchmark
Build your benchmark:terminal
terminal
fibonacci(30) takes about 2.74 milliseconds on average.
Benchmarking with Parameters
So far, we’ve only tested our function with a single input (n=30). But what if we want to see how performance changes with different input sizes? This is whereDenseRange comes in.
Let’s add a parameterized benchmark to test Fibonacci with various input sizes.
Update your main.cpp to include:
state.range(0) gives us the input parameter, and DenseRange(15, 35, 5)
tells the benchmark to run with inputs 15, 20, 25, 30, and 35.
Rebuild and run:
terminal
Multiple Arguments
What if your function takes multiple parameters? For example, let’s benchmark the performance ofstd::string::find() with varying text and pattern sizes.
Let’s add a new benchmark to main.cpp:
ArgsProduct() function creates benchmarks for all combinations of the
provided argument lists. In this case, it generates 6 benchmarks (3 text sizes ×
2 pattern sizes), letting you analyze how both parameters affect performance.
Here is the output when you run this benchmark:
terminal
Benchmarking Only What Matters
Sometimes you have expensive setup that shouldn’t be included in your benchmark measurements. For example, loading data from a file or creating large data structures. Google Benchmark provides several ways to handle this.Fresh Setup per Iteration
Let’s benchmark a sorting algorithm where we need fresh data for each iteration. We do not want the data generation time to be included in the benchmark. We can exclude it usingPauseTiming() and ResumeTiming():
std::sort() call is measured.
Shared Setup for All Iterations
When you can reuse the same data across iterations, fixtures are more efficient. They are a class that defines a setup and teardown process that runs once for all iterations. Both of these methods are not included in the timing. Here is an example where we set up a sorted vector once for all iterations and benchmark binary search on it:SetUp() method initializes a sorted vector once before
all iterations, and TearDown() cleans up afterward. The benchmark only
measures the std::binary_search() calls. Fixtures use different macros:
BENCHMARK_DEFINE_F to define and BENCHMARK_REGISTER_F to register with
parameters.
Best Practices
Prevent Compiler Optimizations
The C++ compiler is extremely aggressive with optimizations. Always protect your benchmarks:Keep Benchmarks Deterministic
Use fixed seeds for random number generators:Benchmark Real-World Code
In real projects, you’ll benchmark functions from your library. Here’s a typical structure for a C++ project with benchmarks:terminal
include/mylib/algorithms.hpp defines your library’s API:
src/algorithms.cpp contains the actual algorithm:
benchmarks/bench_algorithms.cpp tests the bubble sort function:
CMakeLists.txt to build both your library and benchmarks:
CMakeLists.txt
terminal
Running Benchmarks Continuously with CodSpeed
So far, you’ve been running benchmarks locally. But local benchmarking has limitations:- Inconsistent hardware: Different developers get different results
- Manual process: Easy to forget to run benchmarks before merging
- No historical tracking: Hard to spot gradual performance degradation
- No PR context: Can’t see performance impact during code review
- Automated performance regression detection in PRs
- Consistent metrics with reliable measurements across all runs
- Historical tracking to see performance over time with detailed charts
- Flamegraph profiles to see exactly what changed in your code’s execution
How to set up CodSpeed with google_benchmark
Here’s how to integrate CodSpeed with yourgoogle_benchmark benchmarks using
CMake:
Build and run the benchmarks locally with CodSpeed enabled
CodSpeed provides a special build mode that instruments your benchmarks for performance tracking.This is controlled with the Run the benchmarks to verify everything works:You should see output indicating CodSpeed is enabled:
CODSPEED_MODE CMake flag, which can be set to:off: (default) Regular benchmarking without CodSpeedsimulation: CodSpeed CPU simulation mode for CIwalltime: Walltime measurements (see walltime docs)
terminal
Notice there are no timing measurements in the local output. CodSpeed only
captures actual performance data when running in CI.
Check the Results
Once the workflow runs, your pull requests will receive a performance report
comment:



Using Bazel?If you’re using Bazel as your build system, check out the
Bazel integration documentation for detailed setup
instructions with CodSpeed.
Next Steps
Check out these resources to continue your C++ benchmarking journey:Get Started with CodSpeed
Sign up and start tracking your C++ performance in CI
CodSpeed C++ Benchmarking Docs
Explore the full google_benchmark API reference
Performance Profiling
Learn how to use flamegraphs to optimize your code
Google Benchmark User Guide
Explore all of google_benchmark’s features in depth
