Index
Benchmark
- Assembly Tests
- Bazel
- Dependencies
- Perf Counters
- Platform Specific Build Instructions
- Python Bindings
- Random Interleaving
- Reducing Variance
- Releasing
- Tools
- User Guide
AssemblyTests
Assembly Tests
The Benchmark library provides a number of functions whose primary
purpose in to affect assembly generation, including DoNotOptimize
and ClobberMemory. In addition there are other functions,
such as KeepRunning, for which generating good assembly is paramount.
For these functions it's important to have tests that verify the
correctness and quality of the implementation. This requires testing
the code generated by the compiler.
This document describes how the Benchmark library tests compiler output,
as well as how to properly write new tests.
Anatomy of a Test
Writing a test has two steps:
- Write the code you want to generate assembly for.
- Add
// CHECKlines to match against the verified assembly.
Example:
// CHECK-LABEL: test_add:
extern "C" int test_add() {
extern int ExternInt;
return ExternInt + 1;
// CHECK: movl ExternInt(%rip), %eax
// CHECK: addl %eax
// CHECK: ret
}LLVM Filecheck
LLVM's Filecheck
is used to test the generated assembly against the // CHECK lines
specified in the tests source file. Please see the documentation
linked above for information on how to write CHECK directives.
Tips and Tricks:
Tests should match the minimal amount of output required to establish
correctness.CHECKdirectives don't have to match on the exact next line
after the previous match, so tests should omit checks for unimportant
bits of assembly. (CHECK-NEXT
can be used to ensure a match occurs exactly after the previous match).The tests are compiled with
-O3 -g0. So we're only testing the
optimized output.The assembly output is further cleaned up using
tools/strip_asm.py.
This removes comments, assembler directives, and unused labels before
the test is run.The generated and stripped assembly file for a test is output under
/test/<test-name>.sFilecheck supports using
CHECKprefixes
to specify lines that should only match in certain situations.
The Benchmark tests useCHECK-CLANGandCHECK-GNUfor lines that
are only expected to match Clang or GCC's output respectively. NormalCHECKlines match against all compilers. (Note:CHECK-NOTandCHECK-LABELare NOT prefixes. They are versions of non-prefixedCHECKlines)Use
extern "C"to disable name mangling for specific functions. This
makes them easier to name in theCHECKlines.
Problems Writing Portable Tests
Writing tests which check the code generated by a compiler are
inherently non-portable. Different compilers and even different compiler
versions may generate entirely different code. The Benchmark tests
must tolerate this.
LLVM Filecheck provides a number of mechanisms to help write
"more portable" tests; including matching using regular expressions,
allowing the creation of named variables
for later matching, and checking non-sequential matches.
Capturing Variables
For example, say GCC stores a variable in a register but Clang stores
it in memory. To write a test that tolerates both cases we "capture"
the destination of the store, and then use the captured expression
to write the remainder of the test.
// CHECK-LABEL: test_div_no_op_into_shr:
extern "C" void test_div_no_op_into_shr(int value) {
int divisor = 2;
benchmark::DoNotOptimize(divisor); // hide the value from the optimizer
return value / divisor;
// CHECK: movl $2, [[DEST:.*]]
// CHECK: idivl [[DEST]]
// CHECK: ret
}Using Regular Expressions to Match Differing Output
Often tests require testing assembly lines which may subtly differ
between compilers or compiler versions. A common example of this
is matching stack frame addresses. In this case regular expressions
can be used to match the differing bits of output. For example:
int ExternInt;
struct Point { int x, y, z; };
// CHECK-LABEL: test_store_point:
extern "C" void test_store_point() {
Point p{ExternInt, ExternInt, ExternInt};
benchmark::DoNotOptimize(p);
// CHECK: movl ExternInt(%rip), %eax
// CHECK: movl %eax, -{{[0-9]+}}(%rsp)
// CHECK: movl %eax, -{{[0-9]+}}(%rsp)
// CHECK: movl %eax, -{{[0-9]+}}(%rsp)
// CHECK: ret
}Current Requirements and Limitations
The tests require Filecheck to be installed along the PATH of the
build machine. Otherwise the tests will be disabled.
Additionally, as mentioned in the previous section, codegen tests are
inherently non-portable. Currently the tests are limited to:
- x86_64 targets.
- Compiled with GCC or Clang
Further work could be done, at least on a limited basis, to extend the
tests to other architectures and compilers (using CHECK prefixes).
Furthermore, the tests fail for builds which specify additional flags
that modify code generation, including --coverage or -fsanitize=.
Bazel
Bazel
Google Benchmark provides Bazel targets for both the benchmark library and the
optional default main function:
@google_benchmark//:benchmarkprovides the benchmark library.@google_benchmark//:benchmark_mainprovides the defaultmainfunction and
depends on@google_benchmark//:benchmark.
Use @google_benchmark//:benchmark when the benchmark target defines its ownmain function, including through BENCHMARK_MAIN(). Use@google_benchmark//:benchmark_main when the benchmark target should use the
default Google Benchmark entry point.
Bzlmod
With Bzlmod enabled, add Google Benchmark to your MODULE.bazel file:
bazel_dep(name = "google_benchmark", version = "<VERSION>")Replace <VERSION> with the Google Benchmark release version you want to use.
Then depend on the Bazel target from a cc_binary or cc_test:
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "string_benchmark",
srcs = ["string_benchmark.cc"],
deps = ["@google_benchmark//:benchmark_main"],
)The source file should register benchmarks, but it should not callBENCHMARK_MAIN() when linking against @google_benchmark//:benchmark_main:
#include <benchmark/benchmark.h>
#include <string>
static void BM_StringCreation(benchmark::State& state) {
for (auto _ : state) {
std::string empty_string;
}
}
BENCHMARK(BM_StringCreation);Run the benchmark with Bazel:
bazel run //:string_benchmarkPass Google Benchmark flags after Bazel's -- separator:
bazel run //:string_benchmark -- --benchmark_filter=StringCreationWORKSPACE
Projects that still use WORKSPACE can declare Google Benchmark as an external
repository and load its dependencies from bazel/benchmark_deps.bzl:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "google_benchmark",
strip_prefix = "benchmark-<VERSION>",
urls = ["https://github.com/google/benchmark/archive/refs/tags/v<VERSION>.tar.gz"],
# Add sha256 for reproducible builds.
)
load("@google_benchmark//:bazel/benchmark_deps.bzl", "benchmark_deps")
benchmark_deps()Use the same <VERSION> value without the leading v; the archive URL adds the tag prefix explicitly.
After declaring the repository, use the same target labels shown above:@google_benchmark//:benchmark or @google_benchmark//:benchmark_main.
Perf Counters
When using Bazel, enable libpfm support by adding --define pfm=1 to the build
or run command. See Perf Counters for more details.
Dependencies
Build tool dependency policy
We follow the Foundational C++ support policy for our build tools. In
particular the "Build Systems" section.
CMake
The current supported version is CMake 3.13 as of 2024-10-24. Most modern
distributions include newer versions, for example:
- Ubuntu 20.04 provides CMake 3.16.3
- Debian 11.4 provides CMake 3.18.4
- Ubuntu 22.04 provides CMake 3.22.1
Python
The Python bindings require Python 3.10+ as of v1.9.0 (2024-08-16) for installation from PyPI.
Building from source for older versions probably still works, though. See the user guide for details on how to build from source.
The minimum theoretically supported version is Python 3.8, since the used bindings generator (nanobind) only supports Python 3.8+.
Perf Counters
User-Requested Performance Counters
When running benchmarks, the user may choose to request collection of
performance counters. This may be useful in investigation scenarios - narrowing
down the cause of a regression; or verifying that the underlying cause of a
performance improvement matches expectations.
This feature is available if:
- The benchmark is run on an architecture featuring a Performance Monitoring
Unit (PMU), - The benchmark is compiled with support for collecting counters. Currently,
this requires libpfm, which is built as a
dependency via Bazel.
The feature does not require modifying benchmark code. Counter collection is
handled at the boundaries where timer collection is also handled.
To opt-in:
- If using a Bazel build, add
--define pfm=1to your build flags - If using CMake:
- Install
libpfm4-dev, e.g.apt-get install libpfm4-dev. - Enable the CMake flag
BENCHMARK_ENABLE_LIBPFMinCMakeLists.txt.
- Install
To use, pass a comma-separated list of counter names through the--benchmark_perf_counters flag. The names are decoded through libpfm - meaning,
they are platform specific, but some (e.g. CYCLES or INSTRUCTIONS) are
mapped by libpfm to platform-specifics - see libpfm
documentation for more details.
The counter values are reported back through the User Counters
mechanism, meaning, they are available in all the formats (e.g. JSON) supported
by User Counters.
Platform Specific Build Instructions
Platform Specific Build Instructions
Building with GCC
When the library is built using GCC it is necessary to link with the pthread
library due to how GCC implements std::thread. Failing to link to pthread will
lead to runtime exceptions (unless you're using libc++), not linker errors. See
issue #67 for more details. You
can link to pthread by adding -pthread to your linker command. Note, you can
also use -lpthread, but there are potential issues with ordering of command
line parameters if you use that.
On QNX, the pthread library is part of libc and usually included automatically
(seepthread_create()).
There's no separate pthread library to link.
Building with Visual Studio 2015, 2017 or 2022
The shlwapi library (-lshlwapi) is required to support a call to CPUInfo which reads the registry. Either add shlwapi.lib under [ Configuration Properties > Linker > Input ], or use the following:
// Alternatively, can add libraries using linker options.
// First, Add the path to the generated library files (directory containing the `benchmark.lib`) in `[Configuration Properties > Linker > General > Additional Library Directories]`. Then do the following:
#ifdef _WIN32
#pragma comment ( lib, "Shlwapi.lib" )
#ifdef _DEBUG
#pragma comment ( lib, "benchmark.lib" )
#else
#pragma comment ( lib, "benchmark.lib" )
#endif
#endifWhen using the static library, make sure to add BENCHMARK_STATIC_DEFINE under [Configuration Properties > C/C++ > Preprocessor > Preprocessor Definitions]
Can also use the graphical version of CMake:
- Open
CMake GUI. - Under
Where to build the binaries, same path as source plusbuild. - Under
CMAKE_INSTALL_PREFIX, same path as source plusinstall. - Click
Configure,Generate,Open Project. - If build fails, try deleting entire directory and starting again, or unticking options to build less.
Building with Intel 2015 Update 1 or Intel System Studio Update 4
See instructions for building with Visual Studio. Once built, right click on the solution and change the build to Intel.
Building on Solaris
If you're running benchmarks on solaris, you'll want the kstat library linked in
too (-lkstat).
Python Bindings
Building and installing Python bindings
Python bindings are available as wheels on PyPI for importing and
using Google Benchmark directly in Python.
Currently, pre-built wheels exist for macOS (both ARM64 and Intel x86), Linux x86-64 and 64-bit Windows.
Supported Python versions are Python 3.8 - 3.12.
To install Google Benchmark's Python bindings, run:
python -m pip install --upgrade pip # for manylinux2014 support
python -m pip install google-benchmarkIn order to keep your system Python interpreter clean, it is advisable to run these commands in a virtual
environment. See the official Python documentation
on how to create virtual environments.
To build a wheel directly from source, you can follow these steps:
git clone https://github.com/google/benchmark.git
cd benchmark
# create a virtual environment and activate it
python3 -m venv venv --system-site-packages
source venv/bin/activate # .\venv\Scripts\Activate.ps1 on Windows
# upgrade Python's system-wide packages
python -m pip install --upgrade pip build
# builds the wheel and stores it in the directory "dist".
python -m buildNB: Building wheels from source requires Bazel. For platform-specific instructions on how to install Bazel,
refer to the Bazel installation docs.
Random Interleaving
Random Interleaving
Random Interleaving is a
technique to lower run-to-run variance. It randomly interleaves repetitions of a
microbenchmark with repetitions from other microbenchmarks in the same benchmark
test. Data shows it is able to lower run-to-run variance by
40% on average.
To use, you mainly need to set --benchmark_enable_random_interleaving=true,
and optionally specify non-zero repetition count --benchmark_repetitions=9
and optionally decrease the per-repetition time --benchmark_min_time=0.1.
Reducing Variance
Reducing Variance
Disabling CPU Frequency Scaling
If you see this error:
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.you might want to disable the CPU frequency scaling while running the
benchmark, as well as consider other ways to stabilize the performance of
your system while benchmarking.
Exactly how to do this depends on the Linux distribution,
desktop environment, and installed programs. Specific details are a moving
target, so we will not attempt to exhaustively document them here.
One simple option is to use the cpupower program to change the
performance governor to "performance". This tool is maintained along with
the Linux kernel and provided by your distribution.
It must be run as root, like this:
sudo cpupower frequency-set --governor performanceAfter this you can verify that all CPUs are using the performance governor
by running this command:
cpupower frequency-info -o procThe benchmarks you subsequently run will have less variance.
Disabling ASLR
If you see this error:
***WARNING*** ASLR is enabled, the results may have unreproducible noise in them.you might want to disable the ASLR security hardening feature while running the
benchmark.
The simplest way is to add
benchmark::MaybeReenterWithoutASLR(argc, argv);as the first line of your main() function. It will try to disable ASLR
for the current processor, and, if successful, re-execute the binary.
Note that personality(2) may be forbidden by e.g. seccomp (which happens
by default if you are running in a Docker container).
Note that if you link to benchmark_main already does that for you.
To globally disable ASLR on Linux, run
echo 0 > /proc/sys/kernel/randomize_va_spaceTo run a single benchmark with ASLR disabled on Linux, do:
setarch `uname -m` -R ./a_benchmarkNote that for the information on how to disable ASLR on other operating systems,
please refer to their documentation.
Reducing Variance in Benchmarks
The Linux CPU frequency governor discussed
above is not the only source
of noise in benchmarks. Some, but not all, of the sources of variance
include:
- On multi-core machines not all CPUs/CPU cores/CPU threads run the same
speed, so running a benchmark one time and then again may give a
different result depending on which CPU it ran on. - CPU scaling features that run on the CPU, like Intel's Turbo Boost and
AMD Turbo Core and Precision Boost, can temporarily change the CPU
frequency even when the using the "performance" governor on Linux. - Context switching between CPUs, or scheduling competition on the CPU the
benchmark is running on. - Intel Hyperthreading or AMD SMT causing the same issue as above.
- Cache effects caused by code running on other CPUs.
- Non-uniform memory architectures (NUMA).
These can cause variance in benchmarks results within a single run
(--benchmark_repetitions=N) or across multiple runs of the benchmark
program.
Reducing sources of variance is OS and architecture dependent, which is one
reason some companies maintain machines dedicated to performance testing.
Some of the easier and effective ways of reducing variance on a typical
Linux workstation are:
- Use the performance governor as discussed
above. - Disable processor boosting by:See the Linux kernel'ssh
echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost
boost.txt
for more information. - Set the benchmark program's task affinity to a fixed cpu. For example:sh
taskset -c 0 ./mybenchmark - Increase the program's scheduling priority to minimize context switches using
niceorchrt:shsudo nice -n -20 ./mybenchmark sudo chrt -f 80 ./mybenchmark - Disabling Hyperthreading/SMT. This can be done in the Bios or using the
/sysfile system (see the LLVM project's Benchmarking
tips). - Close other programs that do non-trivial things based on timers, such as
your web browser, desktop environment, etc. - Reduce the working set of your benchmark to fit within the L1 cache, but
do be aware that this may lead you to optimize for an unrealistic
situation.
Further resources on this topic:
- The LLVM project's Benchmarking
tips. - The Arch Wiki Cpu frequency
scaling page.
Releasing
How to release
- Make sure you're on main and synced to HEAD
- Ensure the project builds and tests run
parallel -j0 exec ::: test/*_testcan help ensure everything at least
passes
- Prepare release notes
git log $(git describe --abbrev=0 --tags)..HEADgives you the list of
commits between the last annotated tag and HEAD- Pick the most interesting.
- Create one last commit that updates the version saved in
CMakeLists.txt,MODULE.bazel,bindings/python/google_benchmark/__init__.py, andbindings/rust/Cargo.tomlto the release version you're creating.
(This version will be used if benchmark is installed from the archive you'll be creating
in the next step.)
# CMakeLists.txt
project (benchmark VERSION 1.9.0 LANGUAGES CXX)# MODULE.bazel
module(name = "com_github_google_benchmark", version="1.9.0")# google_benchmark/__init__.py
__version__ = "1.9.0"# bindings/rust/Cargo.toml
version = "1.9.0"- Create a release through github's interface
- Note this will create a lightweight tag.
- Update this to an annotated tag:
git pull --tagsgit tag -a -f <tag> <tag>git push --force --tags origin
- Confirm that the "Build and upload Python wheels" action runs to completion
- Run it manually if it hasn't run.
Tools
Benchmark Tools
compare.py
The compare.py can be used to compare the result of benchmarks.
Dependencies
The utility relies on the scipy package which can be installed using pip:
pip3 install -r requirements.txtDisplaying aggregates only
The switch -a / --display_aggregates_only can be used to control the
displayment of the normal iterations vs the aggregates. When passed, it will
be passthrough to the benchmark binaries to be run, and will be accounted for
in the tool itself; only the aggregates will be displayed, but not normal runs.
It only affects the display, the separate runs will still be used to calculate
the U test.
Modes of operation
There are three modes of operation:
- Just compare two benchmarks
The program is invoked like:
$ compare.py benchmarks <benchmark_baseline> <benchmark_contender> [benchmark options]...Where and either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file.
[benchmark options] will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal --benchmark_* parameters, or some custom parameters your binary takes.
Example output:
/* Detailed source-code truncated for AI context efficiency. */What it does is for the every benchmark from the first run it looks for the benchmark with exactly the same name in the second run, and then compares the results. If the names differ, the benchmark is omitted from the diff.
As you can note, the values in Time and CPU columns are calculated as (new - old) / |old|.
- Compare two different filters of one benchmark
The program is invoked like:
$ compare.py filters <benchmark> <filter_baseline> <filter_contender> [benchmark options]...Where `` either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file.
Where <filter_baseline> and <filter_contender> are the same regex filters that you would pass to the [--benchmark_filter=<regex>] parameter of the benchmark binary.
[benchmark options] will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal --benchmark_* parameters, or some custom parameters your binary takes.
Example output:
/* Detailed source-code truncated for AI context efficiency. */As you can see, it applies filter to the benchmarks, both when running the benchmark, and before doing the diff. And to make the diff work, the matches are replaced with some common string. Thus, you can compare two different benchmark families within one benchmark binary.
As you can note, the values in Time and CPU columns are calculated as (new - old) / |old|.
- Compare filter one from benchmark one to filter two from benchmark two:
The program is invoked like:
$ compare.py filters <benchmark_baseline> <filter_baseline> <benchmark_contender> <filter_contender> [benchmark options]...Where and either specify a benchmark executable file, or a JSON output file. The type of the input file is automatically detected. If a benchmark executable is specified then the benchmark is run to obtain the results. Otherwise the results are simply loaded from the output file.
Where <filter_baseline> and <filter_contender> are the same regex filters that you would pass to the [--benchmark_filter=<regex>] parameter of the benchmark binary.
[benchmark options] will be passed to the benchmarks invocations. They can be anything that binary accepts, be it either normal --benchmark_* parameters, or some custom parameters your binary takes.
Example output:
/* Detailed source-code truncated for AI context efficiency. */This is a mix of the previous two modes, two (potentially different) benchmark binaries are run, and a different filter is applied to each one.
As you can note, the values in Time and CPU columns are calculated as (new - old) / |old|.
Note: Interpreting the output
Performance measurements are an art, and performance comparisons are doubly so.
Results are often noisy and don't necessarily have large absolute differences to
them, so just by visual inspection, it is not at all apparent if two
measurements are actually showing a performance change or not. It is even more
confusing with multiple benchmark repetitions.
Thankfully, what we can do, is use statistical tests on the results to determine
whether the performance has statistically-significantly changed. compare.py
uses MannโWhitney U
test, with a null
hypothesis being that there's no difference in performance.
The below output is a summary of a benchmark comparison with statistics
provided for a multi-threaded process.
Benchmark Time CPU Time Old Time New CPU Old CPU New
-----------------------------------------------------------------------------------------------------------------------------
benchmark/threads:1/process_time/real_time_pvalue 0.0000 0.0000 U Test, Repetitions: 27 vs 27
benchmark/threads:1/process_time/real_time_mean -0.1442 -0.1442 90 77 90 77
benchmark/threads:1/process_time/real_time_median -0.1444 -0.1444 90 77 90 77
benchmark/threads:1/process_time/real_time_stddev +0.3974 +0.3933 0 0 0 0
benchmark/threads:1/process_time/real_time_cv +0.6329 +0.6280 0 0 0 0
OVERALL_GEOMEAN -0.1442 -0.1442 0 0 0 0Here's a breakdown of each row:
benchmark/threads:1/process_time/real_time_pvalue: This shows the p-value for
the statistical test comparing the performance of the process running with one
thread. A value of 0.0000 suggests a statistically significant difference in
performance. The comparison was conducted using the U Test (Mann-Whitney
U Test) with 27 repetitions for each case.
benchmark/threads:1/process_time/real_time_mean: This shows the relative
difference in mean execution time between two different cases. The negative
value (-0.1442) implies that the new process is faster by about 14.42%. The old
time was 90 units, while the new time is 77 units.
benchmark/threads:1/process_time/real_time_median: Similarly, this shows the
relative difference in the median execution time. Again, the new process is
faster by 14.44%.
benchmark/threads:1/process_time/real_time_stddev: This is the relative
difference in the standard deviation of the execution time, which is a measure
of how much variation or dispersion there is from the mean. A positive value
(+0.3974) implies there is more variance in the execution time in the new
process.
benchmark/threads:1/process_time/real_time_cv: CV stands for Coefficient of
Variation. It is the ratio of the standard deviation to the mean. It provides a
standardized measure of dispersion. An increase (+0.6329) indicates more
relative variability in the new process.
OVERALL_GEOMEAN: Geomean stands for geometric mean, a type of average that is
less influenced by outliers. The negative value indicates a general improvement
in the new process. However, given the values are all zero for the old and new
times, this seems to be a mistake or placeholder in the output.
Let's first try to see what the different columns represent in the abovecompare.py benchmarking output:
Benchmark: The name of the function being benchmarked, along with the
size of the input (after the slash).Time: The average time per operation, across all iterations.
CPU: The average CPU time per operation, across all iterations.
Iterations: The number of iterations the benchmark was run to get a
stable estimate.Time Old and Time New: These represent the average time it takes for a
function to run in two different scenarios or versions. For example, you
might be comparing how fast a function runs before and after you make some
changes to it.CPU Old and CPU New: These show the average amount of CPU time that the
function uses in two different scenarios or versions. This is similar to
Time Old and Time New, but focuses on CPU usage instead of overall time.
In the comparison section, the relative differences in both time and CPU time
are displayed for each input size.
A statistically-significant difference is determined by a p-value, which is
a measure of the probability that the observed difference could have occurred
just by random chance. A smaller p-value indicates stronger evidence against the
null hypothesis.
Therefore:
- If the p-value is less than the chosen significance level (alpha), we
reject the null hypothesis and conclude the benchmarks are significantly
different. - If the p-value is greater than or equal to alpha, we fail to reject the
null hypothesis and treat the two benchmarks as similar.
The result of said the statistical test is additionally communicated through color coding:
+ Green: The benchmarks are statistically different. This could mean the
performance has either significantly improved or significantly
deteriorated. You should look at the actual performance numbers to see which
is the case.
- Red: The benchmarks are statistically similar. This means the performance
hasn't significantly changed.
In statistical terms, 'green' means we reject the null hypothesis that
there's no difference in performance, and 'red' means we fail to reject the
null hypothesis. This might seem counter-intuitive if you're expecting 'green'
to mean 'improved performance' and 'red' to mean 'worsened performance'.
But remember, in this context:
'Success' means 'successfully finding a difference'.
'Failure' means 'failing to find a difference'.Also, please note that even if we determine that there is a
statistically-significant difference between the two measurements, it does not
necessarily mean that the actual benchmarks that were measured are
different, or vice versa, even if we determine that there is no
statistically-significant difference between the two measurements, it does not
necessarily mean that the actual benchmarks that were measured are not
different.
U test
If there is a sufficient repetition count of the benchmarks, the tool can do
a U Test, of the
null hypothesis that it is equally likely that a randomly selected value from
one sample will be less than or greater than a randomly selected value from a
second sample.
If the calculated p-value is below this value is lower than the significance
level alpha, then the result is said to be statistically significant and the
null hypothesis is rejected. Which in other words means that the two benchmarks
aren't identical.
WARNING: requires LARGE (no less than 9) number of repetitions to be
meaningful!
User Guide
User Guide
Command Line
Running a Subset of Benchmarks
Library
Runtime and Reporting Considerations
Calculating Asymptotic Complexity
Templated Benchmarks that take arguments
User-Requested Performance Counters
Benchmarking Tips
Disabling CPU Frequency Scaling
Reducing Variance in Benchmarks
Command Line Options
Benchmarks accept options that may be specified either through their command line interface or by setting environment variables before execution. For every --option_flag=<value> CLI switch, a corresponding environment variable OPTION_FLAG=<value> exists and is used as default if set (CLI switches always prevail).
Benchmark Selection and Execution
`--benchmark_list_tests` (BENCHMARK_LIST_TESTS)
Print a list of all benchmark names and exit. This option overrides all other options.
Example:
$ ./benchmark --benchmark_list_tests
BM_SomeFunction
BM_AnotherFunction`--benchmark_filter=` (BENCHMARK_FILTER)
A regular expression that specifies the set of benchmarks to execute. If this flag is empty, or if this flag is the string "all", all benchmarks linked into the binary are run.
Example:
$ ./benchmark --benchmark_filter=BM_memcpy/32`--benchmark_dry_run` (BENCHMARK_DRY_RUN)
If enabled, forces each benchmark to execute exactly one iteration and one repetition, bypassing any configured MinTime(), MinWarmUpTime(), Iterations(), or Repetitions(). This is useful for quickly verifying that benchmarks can run successfully without waiting for full execution.
Example:
$ ./benchmark --benchmark_dry_run`--benchmark_enable_random_interleaving` (BENCHMARK_ENABLE_RANDOM_INTERLEAVING)
If set, enable random interleaving of repetitions of all benchmarks. This can help reduce the impact of system state changes on benchmark results. See GitHub issue #1051 for details.
Example:
$ ./benchmark --benchmark_enable_random_interleavingTiming and Repetition Control
`--benchmark_min_time=` (BENCHMARK_MIN_TIME)
Specifies the minimum amount of time (in seconds) that each benchmark should run. For CPU-time based tests, this is the lower bound on the total CPU time used by all threads that make up the test. For real-time based tests, this is the lower bound on the elapsed time of the benchmark execution, regardless of number of threads.
Default: 0.5 seconds
Example:
$ ./benchmark --benchmark_min_time=1.0`--benchmark_min_warmup_time=` (BENCHMARK_MIN_WARMUP_TIME)
Minimum number of seconds a benchmark should be run before results should be taken into account. This can be necessary for benchmarks of code which needs to fill some form of cache before performance is of interest. Results gathered within this period are discarded and not used for the reported result.
Default: 0.0 seconds
Example:
$ ./benchmark --benchmark_min_warmup_time=0.5`--benchmark_repetitions=` (BENCHMARK_REPETITIONS)
The number of runs of each benchmark. If greater than 1, the mean and standard deviation of the runs will be reported.
Default: 1
Example:
$ ./benchmark --benchmark_repetitions=5Output Formatting
`--benchmark_format=` (BENCHMARK_FORMAT)
The format to use for console output. Valid values are 'console', 'json', or 'csv'. See Output Formats for more details.
Default: console
Example:
$ ./benchmark --benchmark_format=json`--benchmark_out=` (BENCHMARK_OUT)
The file to write additional output to. The output format is controlled by --benchmark_out_format. Specifying this option does not suppress console output.
Example:
$ ./benchmark --benchmark_out=results.json`--benchmark_out_format=` (BENCHMARK_OUT_FORMAT)
The format to use for file output specified by --benchmark_out. Valid values are 'console', 'json', or 'csv'.
Default: json
Example:
$ ./benchmark --benchmark_out=results.csv --benchmark_out_format=csv`--benchmark_color=` (BENCHMARK_COLOR)
Whether to use colors in the output. Valid values are 'true'/'yes'/1, 'false'/'no'/0, and 'auto'. 'auto' means to use colors if the output is being sent to a terminal and the TERM environment variable is set to a terminal type that supports colors.
Default: auto
Example:
$ ./benchmark --benchmark_color=false`--benchmark_time_unit=` (BENCHMARK_TIME_UNIT)
Set the default time unit to use for reports. Valid values are 'ns' (nanoseconds), 'us' (microseconds), 'ms' (milliseconds), or 's' (seconds).
Default: (empty, uses automatic selection)
Example:
$ ./benchmark --benchmark_time_unit=usReporting Options
`--benchmark_report_aggregates_only` (BENCHMARK_REPORT_AGGREGATES_ONLY)
When enabled, only the mean, standard deviation, and other statistics are reported for repeated benchmarks. This affects all reporters (both console and file output).
Default: false
Example:
$ ./benchmark --benchmark_repetitions=5 --benchmark_report_aggregates_only`--benchmark_display_aggregates_only` (BENCHMARK_DISPLAY_AGGREGATES_ONLY)
When enabled, only the mean, standard deviation, and other statistics are displayed for repeated benchmarks. Unlike --benchmark_report_aggregates_only, this only affects the display (console) reporter, not the file reporter, which will still contain all output.
Default: false
Example:
$ ./benchmark --benchmark_repetitions=5 --benchmark_display_aggregates_only`--benchmark_counters_tabular` (BENCHMARK_COUNTERS_TABULAR)
Whether to use tabular format when printing user counters to the console. Valid values: 'true'/'yes'/1, 'false'/'no'/0.
Default: false
Example:
$ ./benchmark --benchmark_counters_tabular=truePerformance Counters and Context
`--benchmark_perf_counters=` (BENCHMARK_PERF_COUNTERS)
List of additional performance counters to collect, in libpfm format. For more information about libpfm, see the libpfm documentation.
Example:
$ ./benchmark --benchmark_perf_counters=cycles,instructions,cache-misses`--benchmark_context=` (BENCHMARK_CONTEXT)
Extra context to include in the output, formatted as comma-separated key-value pairs. This context is included in the JSON output's context object.
Example:
$ ./benchmark --benchmark_context=compiler=clang,version=13Miscellaneous
`-v` (V)
The level of verbose logging to output. Higher values produce more verbose output.
Default: 0
Example:
$ ./benchmark -vOutput Formats
The library supports multiple output formats. Use the--benchmark_format=<console|json|csv> flag (or set theBENCHMARK_FORMAT=<console|json|csv> environment variable) to set
the format type. console is the default format.
The Console format is intended to be a human readable format. By default
the format generates color output. Context is output on stderr and the
tabular data on stdout. Example tabular output looks like:
Benchmark Time(ns) CPU(ns) Iterations
----------------------------------------------------------------------
BM_SetInsert/1024/1 28928 29349 23853 133.097kiB/s 33.2742k items/s
BM_SetInsert/1024/8 32065 32913 21375 949.487kiB/s 237.372k items/s
BM_SetInsert/1024/10 33157 33648 21431 1.13369MiB/s 290.225k items/sThe JSON format outputs human readable JSON split into two top level
attributes: context and benchmarks. This format is useful for tools that
need to consume benchmark results without parsing console output.
The context object contains information about the run in general, including
the date, host, CPU, caches, load average, benchmark library version, andjson_schema_version. Extra context added with benchmark::AddCustomContext or--benchmark_context is emitted as additional string fields in context.
The benchmarks array contains an object for each benchmark result. Iteration
results commonly include fields such as name, run_name, run_type,iterations, real_time, cpu_time, time_unit, and threads. Depending on
benchmark configuration, result objects can also include aggregate fields,
asymptotic complexity fields, skip/error fields, memory metrics, labels, user
counters, and user-requested performance counters.
User counters, including rates such as bytes_per_second anditems_per_second, are emitted as additional numeric fields on the benchmark
object. User-requested performance counters are reported the same way.
The JSON output may gain new fields over time. Consumers should ignore unknown
fields and tolerate optional fields being absent. This allows the format to be
extended while preserving compatibility for existing consumers.
An abbreviated example JSON output looks like:
{
"context": {
"date": "2015/03/17-18:40:25",
"host_name": "my-host",
"num_cpus": 40,
"mhz_per_cpu": 2801,
"cpu_scaling_enabled": false,
"caches": [
{
"type": "Data",
"level": 1,
"size": 32768,
"num_sharing": 2
}
],
"load_avg": [],
"library_version": "vX.Y.Z",
"library_build_type": "debug",
"json_schema_version": 1
},
"benchmarks": [
{
"name": "BM_SetInsert/1024/1",
"run_name": "BM_SetInsert/1024/1",
"run_type": "iteration",
"iterations": 94877,
"real_time": 29275,
"cpu_time": 29836,
"time_unit": "ns",
"bytes_per_second": 134066,
"items_per_second": 33516
},
{
"name": "BM_SetInsert/1024/8",
"run_name": "BM_SetInsert/1024/8",
"run_type": "iteration",
"iterations": 21609,
"real_time": 32317,
"cpu_time": 32429,
"time_unit": "ns",
"bytes_per_second": 986770,
"items_per_second": 246693
},
{
"name": "BM_SetInsert/1024/10",
"run_name": "BM_SetInsert/1024/10",
"run_type": "iteration",
"iterations": 21393,
"real_time": 32724,
"cpu_time": 33355,
"time_unit": "ns",
"bytes_per_second": 1199226,
"items_per_second": 299807
}
]
}The CSV format outputs comma-separated values. The context is output on stderr
and the CSV itself on stdout. Example CSV output looks like:
name,iterations,real_time,cpu_time,bytes_per_second,items_per_second,label
"BM_SetInsert/1024/1",65465,17890.7,8407.45,475768,118942,
"BM_SetInsert/1024/8",116606,18810.1,9766.64,3.27646e+06,819115,
"BM_SetInsert/1024/10",106365,17238.4,8421.53,4.74973e+06,1.18743e+06,Output Files
Write benchmark results to a file with the --benchmark_out=<filename> option
(or set BENCHMARK_OUT). Specify the output format with--benchmark_out_format={json|console|csv} (or setBENCHMARK_OUT_FORMAT={json|console|csv}). Note that the 'csv' reporter is
deprecated and the saved .csv file
is not parsable by csv
parsers.
Specifying --benchmark_out does not suppress the console output.
Running Benchmarks
Benchmarks are executed by running the produced binaries. Benchmarks binaries,
by default, accept options that may be specified either through their command
line interface or by setting environment variables before execution. For every--option_flag=<value> CLI switch, a corresponding environment variableOPTION_FLAG=<value> exist and is used as default if set (CLI switches always
prevails). A complete list of CLI options is available running benchmarks
with the --help switch.
Dry runs
To confirm that benchmarks can run successfully without needing to wait for
multiple repetitions and iterations, the --benchmark_dry_run flag can be
used. This will run the benchmarks as normal, but for 1 iteration and 1
repetition only.
Running a Subset of Benchmarks
The --benchmark_filter=<regex> option (or BENCHMARK_FILTER=<regex>
environment variable) can be used to only run the benchmarks that match
the specified <regex>. For example:
$ ./run_benchmarks.x --benchmark_filter=BM_memcpy/32
Run on (1 X 2300 MHz CPU )
2016-06-25 19:34:24
Benchmark Time CPU Iterations
----------------------------------------------------
BM_memcpy/32 11 ns 11 ns 79545455
BM_memcpy/32k 2181 ns 2185 ns 324074
BM_memcpy/32 12 ns 12 ns 54687500
BM_memcpy/32k 1834 ns 1837 ns 357143Disabling Benchmarks
It is possible to temporarily disable benchmarks by renaming the benchmark
function to have the prefix "DISABLED_". This will cause the benchmark to
be skipped at runtime.
Result comparison
It is possible to compare the benchmarking results.
See Additional Tooling Documentation
Extra Context
Sometimes it's useful to add extra context to the content printed before the
results. By default this section includes information about the CPU on which
the benchmarks are running. If you do want to add more context, you can use
the benchmark_context command line flag:
$ ./run_benchmarks --benchmark_context=pwd=`pwd`
Run on (1 x 2300 MHz CPU)
pwd: /home/user/benchmark/
Benchmark Time CPU Iterations
----------------------------------------------------
BM_memcpy/32 11 ns 11 ns 79545455
BM_memcpy/32k 2181 ns 2185 ns 324074You can get the same effect with the API:
benchmark::AddCustomContext("foo", "bar");Note that attempts to add a second value with the same key will fail with an
error message.
Runtime and Reporting Considerations
When the benchmark binary is executed, each benchmark function is run serially.
The number of iterations to run is determined dynamically by running the
benchmark a few times and measuring the time taken and ensuring that the
ultimate result will be statistically stable. As such, faster benchmark
functions will be run for more iterations than slower benchmark functions, and
the number of iterations is thus reported.
In all cases, the number of iterations for which the benchmark is run is
governed by the amount of time the benchmark takes. Concretely, the number of
iterations is at least one, not more than 1e9, until CPU time is greater than
the minimum time, or the wallclock time is 5x minimum time. The minimum time is
set per benchmark by calling MinTime on the registered benchmark object.
The minimum time can also be set for all benchmarks with the--benchmark_min_time=<value> command-line option. This flag supports two
forms:
--benchmark_min_time=<float>ssets the minimum running time for each
benchmark repetition in seconds.--benchmark_min_time=xruns each benchmark repetition for an
explicit number of iterations instead of using the dynamic time-based
iteration selection. This applies to benchmarks that do not already specify
an explicit iteration count in code.
For compatibility, bare numeric values such as --benchmark_min_time=0.5 are
also interpreted as seconds, but the explicit s suffix is preferred for
clarity.
For example:
$ ./run_benchmarks.x --benchmark_min_time=0.5s
$ ./run_benchmarks.x --benchmark_min_time=100xIf a benchmark specifies its own MinTime() or Iterations() in code, those
per-benchmark settings take precedence over the corresponding--benchmark_min_time command-line forms.
Furthermore warming up a benchmark might be necessary in order to get
stable results because of e.g caching effects of the code under benchmark.
Warming up means running the benchmark a given amount of time, before
results are actually taken into account. The amount of time for which
the warmup should be run can be set per benchmark by callingMinWarmUpTime on the registered benchmark object or for all benchmarks
using the --benchmark_min_warmup_time command-line option. Note thatMinWarmUpTime will overwrite the value of --benchmark_min_warmup_time
for the single benchmark. How many iterations the warmup run of each
benchmark takes is determined the same way as described in the paragraph
above. Per default the warmup phase is set to 0 seconds and is therefore
disabled.
Average timings are then reported over the iterations run. If multiple
repetitions are requested using the --benchmark_repetitions command-line
option, or at registration time, the benchmark function will be run several
times and statistical results across these repetitions will also be reported.
As well as the per-benchmark entries, a preamble in the report will include
information about the machine on which the benchmarks are run.
Setup/Teardown
Global setup/teardown specific to each benchmark can be done by
passing a callback to Setup/Teardown:
The setup/teardown callbacks will be invoked once for each benchmark. If the
benchmark is multi-threaded (will run in k threads), they will be invoked
exactly once before each run with k threads.
If the benchmark uses different size groups of threads, the above will be true
for each size group.
Eg.,
static void DoSetup(const benchmark::State& state) {
}
static void DoTeardown(const benchmark::State& state) {
}
static void BM_func(benchmark::State& state) {...}
BENCHMARK(BM_func)->Arg(1)->Arg(3)->Threads(16)->Threads(32)->Setup(DoSetup)->Teardown(DoTeardown);In this example, DoSetup and DoTearDown will be invoked 4 times each,
specifically, once for each of this family:
- BM_func_Arg_1_Threads_16, BM_func_Arg_1_Threads_32
- BM_func_Arg_3_Threads_16, BM_func_Arg_3_Threads_32
Passing Arguments
Sometimes a family of benchmarks can be implemented with just one routine that
takes an extra argument to specify which one of the family of benchmarks to
run. For example, the following code defines a family of benchmarks for
measuring the speed of memcpy() calls of different lengths:
static void BM_memcpy(benchmark::State& state) {
char* src = new char[state.range(0)];
char* dst = new char[state.range(0)];
memset(src, 'x', state.range(0));
for (auto _ : state)
memcpy(dst, src, state.range(0));
state.SetBytesProcessed(int64_t(state.iterations()) *
int64_t(state.range(0)));
delete[] src;
delete[] dst;
}
BENCHMARK(BM_memcpy)->Arg(8)->Arg(64)->Arg(512)->Arg(4<<10)->Arg(8<<10);The preceding code is quite repetitive, and can be replaced with the following
short-hand. The following invocation will pick a few appropriate arguments in
the specified range and will generate a benchmark for each such argument.
BENCHMARK(BM_memcpy)->Range(8, 8<<10);By default the arguments in the range are generated in multiples of eight and
the command above selects [ 8, 64, 512, 4k, 8k ]. In the following code the
range multiplier is changed to multiples of two.
BENCHMARK(BM_memcpy)->RangeMultiplier(2)->Range(8, 8<<10);Now arguments generated are [ 8, 16, 32, 64, 128, 256, 512, 1024, 2k, 4k, 8k ].
The preceding code shows a method of defining a sparse range. The following
example shows a method of defining a dense range. It is then used to benchmark
the performance of std::vector initialization for uniformly increasing sizes.
static void BM_DenseRange(benchmark::State& state) {
for(auto _ : state) {
std::vector<int> v(state.range(0), state.range(0));
auto data = v.data();
benchmark::DoNotOptimize(data);
benchmark::ClobberMemory();
}
}
BENCHMARK(BM_DenseRange)->DenseRange(0, 1024, 128);Now arguments generated are [ 0, 128, 256, 384, 512, 640, 768, 896, 1024 ].
You might have a benchmark that depends on two or more inputs. For example, the
following code defines a family of benchmarks for measuring the speed of set
insertion.
static void BM_SetInsert(benchmark::State& state) {
std::set<int> data;
for (auto _ : state) {
state.PauseTiming();
data = ConstructRandomSet(state.range(0));
state.ResumeTiming();
for (int j = 0; j < state.range(1); ++j)
data.insert(RandomNumber());
}
}
BENCHMARK(BM_SetInsert)
->Args({1<<10, 128})
->Args({2<<10, 128})
->Args({4<<10, 128})
->Args({8<<10, 128})
->Args({1<<10, 512})
->Args({2<<10, 512})
->Args({4<<10, 512})
->Args({8<<10, 512});The preceding code is quite repetitive, and can be replaced with the following
short-hand. The following macro will pick a few appropriate arguments in the
product of the two specified ranges and will generate a benchmark for each such
pair.
BENCHMARK(BM_SetInsert)->Ranges({{1<<10, 8<<10}, {128, 512}});Some benchmarks may require specific argument values that cannot be expressed
with Ranges. In this case, ArgsProduct offers the ability to generate a
benchmark input for each combination in the product of the supplied vectors.
BENCHMARK(BM_SetInsert)
->ArgsProduct({{1<<10, 3<<10, 8<<10}, {20, 40, 60, 80}})
// would generate the same benchmark arguments as
BENCHMARK(BM_SetInsert)
->Args({1<<10, 20})
->Args({3<<10, 20})
->Args({8<<10, 20})
->Args({3<<10, 40})
->Args({8<<10, 40})
->Args({1<<10, 40})
->Args({1<<10, 60})
->Args({3<<10, 60})
->Args({8<<10, 60})
->Args({1<<10, 80})
->Args({3<<10, 80})
->Args({8<<10, 80});For the most common scenarios, helper methods for creating a list of
integers for a given sparse or dense range are provided.
BENCHMARK(BM_SetInsert)
->ArgsProduct({
benchmark::CreateRange(8, 128, /*multi=*/2),
benchmark::CreateDenseRange(1, 4, /*step=*/1)
})
// would generate the same benchmark arguments as
BENCHMARK(BM_SetInsert)
->ArgsProduct({
{8, 16, 32, 64, 128},
{1, 2, 3, 4}
});For more complex patterns of inputs, passing a custom function to Apply allows
programmatic specification of an arbitrary set of arguments on which to run the
benchmark. The following example enumerates a dense range on one parameter,
and a sparse range on the second.
static void CustomArguments(benchmark::Benchmark* b) {
for (int i = 0; i <= 10; ++i)
for (int j = 32; j <= 1024*1024; j *= 8)
b->Args({i, j});
}
BENCHMARK(BM_SetInsert)->Apply(CustomArguments);Naming Benchmark Arguments
When a benchmark takes one or more numeric arguments, the generated benchmark
names can be made easier to read by naming those arguments. Use ArgName for a
single argument and ArgNames for multiple arguments.
BENCHMARK(BM_memcpy)->Range(8, 512)->ArgName("bytes");This changes names such as BM_memcpy/8 and BM_memcpy/512 toBM_memcpy/bytes:8 and BM_memcpy/bytes:512.
For benchmarks with more than one argument, each name labels the corresponding
argument position.
BENCHMARK(BM_SetInsert)
->Args({100, 128})
->Args({200, 512})
->ArgNames({"size", "inserts"});This produces names such as BM_SetInsert/size:100/inserts:128 andBM_SetInsert/size:200/inserts:512. Empty argument names are allowed and leave
that argument value unlabeled, for example ArgNames({"size", ""}) produces
names like BM_SetInsert/size:100/128.
Passing Arbitrary Arguments to a Benchmark
It is possible to define a benchmark that takes an arbitrary number
of extra arguments. The BENCHMARK_CAPTURE(func, test_case_name, ...args)
macro creates a benchmark that invokes func with the benchmark::State as
the first argument followed by the specified args....
The test_case_name is appended to the name of the benchmark and
should describe the values passed.
template <class ...Args>
void BM_takes_args(benchmark::State& state, Args&&... args) {
auto args_tuple = std::make_tuple(std::move(args)...);
for (auto _ : state) {
std::cout << std::get<0>(args_tuple) << ": " << std::get<1>(args_tuple)
<< '\n';
[...]
}
}
// Registers a benchmark named "BM_takes_args/int_string_test" that passes
// the specified values to `args`.
BENCHMARK_CAPTURE(BM_takes_args, int_string_test, 42, std::string("abc"));
// Registers the same benchmark "BM_takes_args/int_test" that passes
// the specified values to `args`.
BENCHMARK_CAPTURE(BM_takes_args, int_test, 42, 43);Note that elements of ...args may refer to global variables. Users should
avoid modifying global state inside of a benchmark.
Naming a Benchmark Without Capturing Arguments
If you only need to give a benchmark a custom name (without passing extra
arguments), use BENCHMARK_NAMED(func, test_case_name). UnlikeBENCHMARK_CAPTURE, this macro does not create a lambda, which avoids
compiler and linker scalability issues when registering thousands of
benchmarks.
void BM_Foo(benchmark::State& state) {
for (auto _ : state) {}
}
// Registers a benchmark named "BM_Foo/my_variant"
BENCHMARK_NAMED(BM_Foo, my_variant);Use BENCHMARK_CAPTURE when you need to pass extra arguments; useBENCHMARK_NAMED when you only need the name.
Calculating Asymptotic Complexity (Big O)
Asymptotic complexity might be calculated for a family of benchmarks. The
following code will calculate the coefficient for the high-order term in the
running time and the normalized root-mean square error of string comparison.
static void BM_StringCompare(benchmark::State& state) {
std::string s1(state.range(0), '-');
std::string s2(state.range(0), '-');
for (auto _ : state) {
auto comparison_result = s1.compare(s2);
benchmark::DoNotOptimize(comparison_result);
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_StringCompare)
->RangeMultiplier(2)->Range(1<<10, 1<<18)->Complexity(benchmark::oN);As shown in the following invocation, asymptotic complexity might also be
calculated automatically.
BENCHMARK(BM_StringCompare)
->RangeMultiplier(2)->Range(1<<10, 1<<18)->Complexity();The following code will specify asymptotic complexity with a lambda function,
that might be used to customize high-order term calculation.
BENCHMARK(BM_StringCompare)->RangeMultiplier(2)
->Range(1<<10, 1<<18)->Complexity([](benchmark::IterationCount n)->double{return n; });Custom Benchmark Name
You can change the benchmark's name as follows:
BENCHMARK(BM_memcpy)->Name("memcpy")->RangeMultiplier(2)->Range(8, 8<<10);The invocation will execute the benchmark as before using BM_memcpy but changes
the prefix in the report to memcpy.
Templated Benchmarks
This example produces and consumes messages of size sizeof(v) range_x
times. It also outputs throughput in the absence of multiprogramming.
template <class Q> void BM_Sequential(benchmark::State& state) {
Q q;
typename Q::value_type v;
for (auto _ : state) {
for (int i = state.range(0); i--; )
q.push(v);
for (int e = state.range(0); e--; )
q.Wait(&v);
}
// actually messages, not bytes:
state.SetBytesProcessed(
static_cast<int64_t>(state.iterations())*state.range(0));
}
// You can use the BENCHMARK macro with template parameters:
BENCHMARK(BM_Sequential<WaitQueue<int>>)->Range(1<<0, 1<<10);
// Old, legacy verbose C++03 syntax:
BENCHMARK_TEMPLATE(BM_Sequential, WaitQueue<int>)->Range(1<<0, 1<<10);Three macros are provided for adding benchmark templates.
#define BENCHMARK(func<...>) // Takes any number of parameters.
#define BENCHMARK_TEMPLATE1(func, arg1)
#define BENCHMARK_TEMPLATE2(func, arg1, arg2)Templated Benchmarks that take arguments
Sometimes there is a need to template benchmarks, and provide arguments to them.
template <class Q> void BM_Sequential_With_Step(benchmark::State& state, int step) {
Q q;
typename Q::value_type v;
for (auto _ : state) {
for (int i = state.range(0); i-=step; )
q.push(v);
for (int e = state.range(0); e-=step; )
q.Wait(&v);
}
// actually messages, not bytes:
state.SetBytesProcessed(
static_cast<int64_t>(state.iterations())*state.range(0));
}
BENCHMARK_TEMPLATE1_CAPTURE(BM_Sequential, WaitQueue<int>, Step1, 1)->Range(1<<0, 1<<10);Fixtures
Fixture tests are created by first defining a type that derives from::benchmark::Fixture and then creating/registering the tests using the
following macros:
BENCHMARK_F(ClassName, Method)BENCHMARK_DEFINE_F(ClassName, Method)BENCHMARK_REGISTER_F(ClassName, Method)
For Example:
class MyFixture : public benchmark::Fixture {
public:
void SetUp(::benchmark::State& state) {
}
void TearDown(::benchmark::State& state) {
}
};
// Defines and registers `FooTest` using the class `MyFixture`.
BENCHMARK_F(MyFixture, FooTest)(benchmark::State& st) {
for (auto _ : st) {
...
}
}
// Only defines `BarTest` using the class `MyFixture`.
BENCHMARK_DEFINE_F(MyFixture, BarTest)(benchmark::State& st) {
for (auto _ : st) {
...
}
}
// `BarTest` is NOT registered.
BENCHMARK_REGISTER_F(MyFixture, BarTest)->Threads(2);
// `BarTest` is now registered.Templated Fixtures
Also you can create templated fixture by using the following macros:
BENCHMARK_TEMPLATE_F(ClassName, Method, ...)BENCHMARK_TEMPLATE_DEFINE_F(ClassName, Method, ...)
For example:
template<typename T>
class MyFixture : public benchmark::Fixture {};
// Defines and registers `IntTest` using the class template `MyFixture<int>`.
BENCHMARK_TEMPLATE_F(MyFixture, IntTest, int)(benchmark::State& st) {
for (auto _ : st) {
...
}
}
// Only defines `DoubleTest` using the class template `MyFixture<double>`.
BENCHMARK_TEMPLATE_DEFINE_F(MyFixture, DoubleTest, double)(benchmark::State& st) {
for (auto _ : st) {
...
}
}
// `DoubleTest` is NOT registered.
BENCHMARK_REGISTER_F(MyFixture, DoubleTest)->Threads(2);
// `DoubleTest` is now registered.If you want to use a method template for your fixtures,
which you instantiate afterward, use the following macros:
BENCHMARK_TEMPLATE_METHOD_F(ClassName, Method)BENCHMARK_TEMPLATE_INSTANTIATE_F(ClassName, Method, ...)
With these macros you can define one method for several instantiations.
Example (using MyFixture from above):
// Defines `Test` using the class template `MyFixture`.
BENCHMARK_TEMPLATE_METHOD_F(MyFixture, Test)(benchmark::State& st) {
for (auto _ : st) {
...
}
}
// Instantiates and registers the benchmark `MyFixture<int>::Test`.
BENCHMARK_TEMPLATE_INSTANTIATE_F(MyFixture, Test, int)->Threads(2);
// Instantiates and registers the benchmark `MyFixture<double>::Test`.
BENCHMARK_TEMPLATE_INSTANTIATE_F(MyFixture, Test, double)->Threads(4);Inside the method definition of BENCHMARK_TEMPLATE_METHOD_F the type Base refers
to the type of the instantiated fixture.
Accesses to members of the fixture must be prefixed by this->.
BENCHMARK_TEMPLATE_METHOD_Fand BENCHMARK_TEMPLATE_INSTANTIATE_F can only be used,
if the fixture does not use non-type template parameters.
If you want to pass values as template parameters, use e.g. std::integral_constant.
For example:
template<typename Sz>
class SizedFixture : public benchmark::Fixture {
static constexpr auto Size = Sz::value;
int myValue;
};
BENCHMARK_TEMPLATE_METHOD_F(SizedFixture, Test)(benchmark::State& st) {
for (auto _ : st) {
this->myValue = Base::Size;
}
}
BENCHMARK_TEMPLATE_INSTANTIATE_F(SizedFixture, Test, std::integral_constant<5>)->Threads(2);Custom Counters
You can add your own counters with user-defined names. The example below
will add columns "Foo", "Bar" and "Baz" in its output:
static void UserCountersExample1(benchmark::State& state) {
double numFoos = 0, numBars = 0, numBazs = 0;
for (auto _ : state) {
// ... count Foo,Bar,Baz events
}
state.counters["Foo"] = numFoos;
state.counters["Bar"] = numBars;
state.counters["Baz"] = numBazs;
}The state.counters object is a std::map with std::string keys
and Counter values. The latter is a double-like class, via an implicit
conversion to double&. Thus you can use all of the standard arithmetic
assignment operators (=,+=,-=,*=,/=) to change the value of each counter.
The Counter constructor accepts three parameters: the value as a double
; a bit flag which allows you to show counters as rates, and/or as per-thread
iteration, and/or as per-thread averages, and/or iteration invariants,
and/or finally inverting the result; and a flag specifying the 'unit' - i.e.
is 1k a 1000 (default, benchmark::Counter::OneK::kIs1000), or 1024
(benchmark::Counter::OneK::kIs1024)?
// sets a simple counter
state.counters["Foo"] = numFoos;
// Set the counter as a rate. It will be presented divided
// by the duration of the benchmark.
// Meaning: per one second, how many 'foo's are processed?
state.counters["FooRate"] = Counter(numFoos, benchmark::Counter::kIsRate);
// Set the counter as a rate. It will be presented divided
// by the duration of the benchmark, and the result inverted.
// Meaning: how many seconds it takes to process one 'foo'?
state.counters["FooInvRate"] = Counter(numFoos, benchmark::Counter::kIsRate | benchmark::Counter::kInvert);
// Set the counter as a thread-average quantity. It will
// be presented divided by the number of threads.
state.counters["FooAvg"] = Counter(numFoos, benchmark::Counter::kAvgThreads);
// There's also a combined flag:
state.counters["FooAvgRate"] = Counter(numFoos,benchmark::Counter::kAvgThreadsRate);
// This says that we process with the rate of state.range(0) bytes every iteration:
state.counters["BytesProcessed"] = Counter(state.range(0), benchmark::Counter::kIsIterationInvariantRate, benchmark::Counter::OneK::kIs1024);You can use insert() with std::initializer_list:
state.counters.insert({{"Foo", numFoos}, {"Bar", numBars}, {"Baz", numBazs}});
// ... instead of:
state.counters["Foo"] = numFoos;
state.counters["Bar"] = numBars;
state.counters["Baz"] = numBazs;In multithreaded benchmarks, each counter is set on the calling thread only.
When the benchmark finishes, the counters from each thread will be summed.
Counters that are configured with kIsRate, will report the average rate across all threads, while kAvgThreadsRate counters will report the average rate per thread.
Counter Reporting
When using the console reporter, by default, user counters are printed at
the end after the table, the same way as bytes_processed anditems_processed. This is best for cases in which there are few counters,
or where there are only a couple of lines per benchmark. Here's an example of
the default output:
------------------------------------------------------------------------------
Benchmark Time CPU Iterations UserCounters...
------------------------------------------------------------------------------
BM_UserCounter/threads:8 2248 ns 10277 ns 68808 Bar=16 Bat=40 Baz=24 Foo=8
BM_UserCounter/threads:1 9797 ns 9788 ns 71523 Bar=2 Bat=5 Baz=3 Foo=1024m
BM_UserCounter/threads:2 4924 ns 9842 ns 71036 Bar=4 Bat=10 Baz=6 Foo=2
BM_UserCounter/threads:4 2589 ns 10284 ns 68012 Bar=8 Bat=20 Baz=12 Foo=4
BM_UserCounter/threads:8 2212 ns 10287 ns 68040 Bar=16 Bat=40 Baz=24 Foo=8
BM_UserCounter/threads:16 1782 ns 10278 ns 68144 Bar=32 Bat=80 Baz=48 Foo=16
BM_UserCounter/threads:32 1291 ns 10296 ns 68256 Bar=64 Bat=160 Baz=96 Foo=32
BM_UserCounter/threads:4 2615 ns 10307 ns 68040 Bar=8 Bat=20 Baz=12 Foo=4
BM_Factorial 26 ns 26 ns 26608979 40320
BM_Factorial/real_time 26 ns 26 ns 26587936 40320
BM_CalculatePiRange/1 16 ns 16 ns 45704255 0
BM_CalculatePiRange/8 73 ns 73 ns 9520927 3.28374
BM_CalculatePiRange/64 609 ns 609 ns 1140647 3.15746
BM_CalculatePiRange/512 4900 ns 4901 ns 142696 3.14355If this doesn't suit you, you can print each counter as a table column by
passing the flag --benchmark_counters_tabular=true to the benchmark
application. This is best for cases in which there are a lot of counters, or
a lot of lines per individual benchmark. Note that this will trigger a
reprinting of the table header any time the counter set changes between
individual benchmarks. Here's an example of corresponding output when--benchmark_counters_tabular=true is passed:
/* Detailed source-code truncated for AI context efficiency. */Note above the additional header printed when the benchmark changes fromBM_UserCounter to BM_Factorial. This is because BM_Factorial does
not have the same counter set as BM_UserCounter.
Multithreaded Benchmarks
In a multithreaded test (benchmark invoked by multiple threads simultaneously),
it is guaranteed that none of the threads will start until all have reached
the start of the benchmark loop, and all will have finished before any thread
exits the benchmark loop. (This behavior is also provided by the KeepRunning()
API) As such, any global setup or teardown can be wrapped in a check against the thread
index:
static void BM_MultiThreaded(benchmark::State& state) {
if (state.thread_index() == 0) {
// Setup code here.
}
for (auto _ : state) {
// Run the test as normal.
}
if (state.thread_index() == 0) {
// Teardown code here.
}
}
BENCHMARK(BM_MultiThreaded)->Threads(2);To run the benchmark across a range of thread counts, instead of Threads, useThreadRange. This takes two parameters (min_threads and max_threads) and
runs the benchmark once for values in the inclusive range. For example:
BENCHMARK(BM_MultiThreaded)->ThreadRange(1, 8);will run BM_MultiThreaded with thread counts 1, 2, 4, and 8.
If the benchmarked code itself uses threads and you want to compare it to
single-threaded code, you may want to use real-time ("wallclock") measurements
for latency comparisons:
BENCHMARK(BM_test)->Range(8, 8<<10)->UseRealTime();Without UseRealTime, CPU time is used by default.
Manual Multithreaded Benchmarks
Google/benchmark uses std::thread as multithreading environment per default.
If you want to use another multithreading environment (e.g. OpenMP), you can provide
a factory function to your benchmark using the ThreadRunner function.
The factory function takes the number of threads as argument and creates a custom class
derived from benchmark::ThreadRunnerBase.
This custom class must override the functionvoid RunThreads(const std::function<void(int)>& fn).RunThreads is called by the main thread and spawns the requested number of threads.
Each spawned thread must call fn(thread_index), where thread_index is its own
thread index. Before RunThreads returns, all spawned threads must be joined.
class OpenMPThreadRunner : public benchmark::ThreadRunnerBase
{
OpenMPThreadRunner(int num_threads)
: num_threads_(num_threads)
{}
void RunThreads(const std::function<void(int)>& fn) final
{
#pragma omp parallel num_threads(num_threads_)
fn(omp_get_thread_num());
}
private:
int num_threads_;
};
BENCHMARK(BM_MultiThreaded)
->ThreadRunner([](int num_threads) {
return std::make_unique<OpenMPThreadRunner>(num_threads);
})
->Threads(1)->Threads(2)->Threads(4);The above example creates a parallel OpenMP region before it enters BM_MultiThreaded.
The actual benchmark code can remain the same and is therefore not tied to a specific
thread runner. The measurement does not include the time for creating and joining the
threads.
CPU Timers
By default, the CPU timer only measures the time spent by the main thread.
If the benchmark itself uses threads internally, this measurement may not
be what you are looking for. Instead, there is a way to measure the total
CPU usage of the process, by all the threads.
void callee(int i);
static void MyMain(int size) {
#pragma omp parallel for
for(int i = 0; i < size; i++)
callee(i);
}
static void BM_OpenMP(benchmark::State& state) {
for (auto _ : state)
MyMain(state.range(0));
}
// Measure the time spent by the main thread, use it to decide for how long to
// run the benchmark loop. Depending on the internal implementation detail may
// measure to anywhere from near-zero (the overhead spent before/after work
// handoff to worker thread[s]) to the whole single-thread time.
BENCHMARK(BM_OpenMP)->Range(8, 8<<10);
// Measure the user-visible time, the wall clock (literally, the time that
// has passed on the clock on the wall), use it to decide for how long to
// run the benchmark loop. This will always be meaningful, and will match the
// time spent by the main thread in single-threaded case, in general decreasing
// with the number of internal threads doing the work.
BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->UseRealTime();
// Measure the total CPU consumption, use it to decide for how long to
// run the benchmark loop. This will always measure to no less than the
// time spent by the main thread in single-threaded case.
BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->MeasureProcessCPUTime();
// A mixture of the last two. Measure the total CPU consumption, but use the
// wall clock to decide for how long to run the benchmark loop.
BENCHMARK(BM_OpenMP)->Range(8, 8<<10)->MeasureProcessCPUTime()->UseRealTime();Controlling Timers
Normally, the entire duration of the work loop (for (auto _ : state) {})
is measured. But sometimes, it is necessary to do some work inside of
that loop, every iteration, but without counting that time to the benchmark time.
That is possible, although it is not recommended, since it has high overhead.
static void BM_SetInsert_With_Timer_Control(benchmark::State& state) {
std::set<int> data;
for (auto _ : state) {
state.PauseTiming(); // Stop timers. They will not count until they are resumed.
data = ConstructRandomSet(state.range(0)); // Do something that should not be measured
state.ResumeTiming(); // And resume timers. They are now counting again.
// The rest will be measured.
for (int j = 0; j < state.range(1); ++j)
data.insert(RandomNumber());
}
}
BENCHMARK(BM_SetInsert_With_Timer_Control)->Ranges({{1<<10, 8<<10}, {128, 512}});For convenience, a ScopedPauseTiming class is provided to manage pausing and
resuming timers within a scope. This is less error-prone than manually callingPauseTiming and ResumeTiming.
static void BM_SetInsert_With_Scoped_Timer_Control(benchmark::State& state) {
std::set<int> data;
for (auto _ : state) {
{
benchmark::ScopedPauseTiming pause(state); // Pauses timing
data = ConstructRandomSet(state.range(0));
} // Timing resumes automatically when 'pause' goes out of scope
// The rest will be measured.
for (int j = 0; j < state.range(1); ++j)
data.insert(RandomNumber());
}
}
BENCHMARK(BM_SetInsert_With_Scoped_Timer_Control)->Ranges({{1<<10, 8<<10}, {128, 512}});Manual Timing
For benchmarking something for which neither CPU time nor real-time are
correct or accurate enough, completely manual timing is supported using
the UseManualTime function.
When UseManualTime is used, the benchmarked code must callSetIterationTime once per iteration of the benchmark loop to
report the manually measured time.
An example use case for this is benchmarking GPU execution (e.g. OpenCL
or CUDA kernels, OpenGL or Vulkan or Direct3D draw calls), which cannot
be accurately measured using CPU time or real-time. Instead, they can be
measured accurately using a dedicated API, and these measurement results
can be reported back with SetIterationTime.
static void BM_ManualTiming(benchmark::State& state) {
int microseconds = state.range(0);
std::chrono::duration<double, std::micro> sleep_duration {
static_cast<double>(microseconds)
};
for (auto _ : state) {
auto start = std::chrono::high_resolution_clock::now();
// Simulate some useful workload with a sleep
std::this_thread::sleep_for(sleep_duration);
auto end = std::chrono::high_resolution_clock::now();
auto elapsed_seconds =
std::chrono::duration_cast<std::chrono::duration<double>>(
end - start);
state.SetIterationTime(elapsed_seconds.count());
}
}
BENCHMARK(BM_ManualTiming)->Range(1, 1<<17)->UseManualTime();Setting the Time Unit
If a benchmark runs a few milliseconds it may be hard to visually compare the
measured times, since the output data is given in nanoseconds per default. In
order to manually set the time unit, you can specify it manually:
BENCHMARK(BM_test)->Unit(benchmark::kMillisecond);Additionally the default time unit can be set globally with the--benchmark_time_unit={ns|us|ms|s} command line argument. The argument only
affects benchmarks where the time unit is not set explicitly.
Preventing Optimization
To prevent a value or expression from being optimized away by the compiler
the benchmark::DoNotOptimize(...) and benchmark::ClobberMemory()
functions can be used.
static void BM_test(benchmark::State& state) {
for (auto _ : state) {
int x = 0;
for (int i=0; i < 64; ++i) {
benchmark::DoNotOptimize(x += i);
}
}
}DoNotOptimize(<expr>) forces the result of <expr> to be stored in either
memory or a register. For GNU based compilers it acts as read/write barrier
for global memory. More specifically it forces the compiler to flush pending
writes to memory and reload any other values as necessary.
Note that DoNotOptimize(<expr>) does not prevent optimizations on <expr>
in any way. <expr> may even be removed entirely when the result is already
known. For example:
// Example 1: `<expr>` is removed entirely.
int foo(int x) { return x + 42; }
while (...) DoNotOptimize(foo(0)); // Optimized to DoNotOptimize(42);
// Example 2: Result of '<expr>' is only reused.
int bar(int) __attribute__((const));
while (...) DoNotOptimize(bar(0)); // Optimized to:
// int __result__ = bar(0);
// while (...) DoNotOptimize(__result__);Since that is not the behaviour the user intended,
such problematic cases will result in a diagnostic
at compile time. The correct approach, for an expression result,
is to use an intermediate local variable:
// Avoid: may call the deprecated const-reference overload.
while (...) DoNotOptimize(foo(0));
// Prefer: materialize the result, then pass the local lvalue.
while (...) {
auto result = foo(0);
DoNotOptimize(result);
}The second tool for preventing optimizations is ClobberMemory(). In essenceClobberMemory() forces the compiler to perform all pending writes to global
memory. Memory managed by block scope objects must be "escaped" usingDoNotOptimize(...) before it can be clobbered. In the below exampleClobberMemory() prevents the call to v.push_back(42) from being optimized
away.
static void BM_vector_push_back(benchmark::State& state) {
for (auto _ : state) {
std::vector<int> v;
v.reserve(1);
auto data = v.data(); // Allow v.data() to be clobbered. Pass as non-const
benchmark::DoNotOptimize(data); // lvalue to avoid undesired compiler optimizations
v.push_back(42);
benchmark::ClobberMemory(); // Force 42 to be written to memory.
}
}Note that ClobberMemory() is only available for GNU or MSVC based compilers.
Statistics: Reporting the Mean, Median and Standard Deviation / Coefficient of variation of Repeated Benchmarks
By default each benchmark is run once and that single result is reported.
However benchmarks are often noisy and a single result may not be representative
of the overall behavior. For this reason it's possible to repeatedly rerun the
benchmark.
The number of runs of each benchmark is specified globally by the--benchmark_repetitions flag or on a per benchmark basis by callingRepetitions on the registered benchmark object. When a benchmark is run more
than once the mean, median, standard deviation and coefficient of variation
of the runs will be reported.
Additionally the --benchmark_report_aggregates_only={true|false},--benchmark_display_aggregates_only={true|false} flags orReportAggregatesOnly(bool), DisplayAggregatesOnly(bool) functions can be
used to change how repeated tests are reported. By default the result of each
repeated run is reported. When report aggregates only option is true,
only the aggregates (i.e. mean, median, standard deviation and coefficient
of variation, maybe complexity measurements if they were requested) of the runs
is reported, to both the reporters - standard output (console), and the file.
However when only the display aggregates only option is true,
only the aggregates are displayed in the standard output, while the file
output still contains everything.
Calling ReportAggregatesOnly(bool) / DisplayAggregatesOnly(bool) on a
registered benchmark object overrides the value of the appropriate flag for that
benchmark.
Custom Statistics
While having these aggregates is nice, this may not be enough for everyone.
For example you may want to know what the largest observation is, e.g. because
you have some real-time constraints. This is easy. The following code will
specify a custom statistic to be calculated, defined by a lambda function.
void BM_spin_empty(benchmark::State& state) {
for (auto _ : state) {
for (int x = 0; x < state.range(0); ++x) {
benchmark::DoNotOptimize(x);
}
}
}
BENCHMARK(BM_spin_empty)
->Repetitions(3) // or add option --benchmark_repetitions=3
->ComputeStatistics("max", [](const std::vector<double>& v) -> double {
return *(std::max_element(std::begin(v), std::end(v)));
})
->Arg(512);While usually the statistics produce values in time units,
you can also produce percentages:
void BM_spin_empty(benchmark::State& state) {
for (auto _ : state) {
for (int x = 0; x < state.range(0); ++x) {
benchmark::DoNotOptimize(x);
}
}
}
BENCHMARK(BM_spin_empty)
->Repetitions(3) // or add option --benchmark_repetitions=3
->ComputeStatistics("ratio", [](const std::vector<double>& v) -> double {
return v.front() / v.back();
}, benchmark::StatisticUnit::kPercentage)
->Arg(512);Memory Usage
It's often useful to also track memory usage for benchmarks, alongside CPU
performance. For this reason, benchmark offers the RegisterMemoryManager
method that allows a custom MemoryManager to be injected.
If set, the MemoryManager::Start and MemoryManager::Stop methods will be
called at the start and end of benchmark runs to allow user code to fill out
a report on the number of allocations, bytes used, etc.
This data will then be reported alongside other performance data, currently
only when using JSON output.
Profiling
It's often useful to also profile benchmarks in particular ways, in addition to
CPU performance. For this reason, benchmark offers the RegisterProfilerManager
method that allows a custom ProfilerManager to be injected.
If set, the ProfilerManager::AfterSetupStart andProfilerManager::BeforeTeardownStop methods will be called at the start and
end of a separate benchmark run to allow user code to collect and report
user-provided profile metrics.
Output collected from this profiling run must be reported separately.
Using RegisterBenchmark(name, fn, args...)
The RegisterBenchmark(name, func, args...) function provides an alternative
way to create and register benchmarks.RegisterBenchmark(name, func, args...) creates, registers, and returns a
pointer to a new benchmark with the specified name that invokesfunc(st, args...) where st is a benchmark::State object.
Unlike the BENCHMARK registration macros, which can only be used at the global
scope, the RegisterBenchmark can be called anywhere. This allows for
benchmark tests to be registered programmatically.
Additionally RegisterBenchmark allows any callable object to be registered
as a benchmark. Including capturing lambdas and function objects.
For Example:
auto BM_test = [](benchmark::State& st, auto Inputs) { /* ... */ };
int main(int argc, char** argv) {
benchmark::MaybeReenterWithoutASLR(argc, argv);
for (auto& test_input : { /* ... */ })
benchmark::RegisterBenchmark(test_input.name(), BM_test, test_input);
benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
benchmark::Shutdown();
}Exiting with an Error
When errors caused by external influences, such as file I/O and network
communication, occur within a benchmark theState::SkipWithError(const std::string& msg) function can be used to skip that run
of benchmark and report the error. Note that only future iterations of theKeepRunning() are skipped. For the ranged-for version of the benchmark loop
Users must explicitly exit the loop, otherwise all iterations will be performed.
Users may explicitly return to exit the benchmark immediately.
The SkipWithError(...) function may be used at any point within the benchmark,
including before and after the benchmark loop. Moreover, if SkipWithError(...)
has been used, it is not required to reach the benchmark loop and one may return
from the benchmark function early.
For example:
static void BM_test(benchmark::State& state) {
auto resource = GetResource();
if (!resource.good()) {
state.SkipWithError("Resource is not good!");
// KeepRunning() loop will not be entered.
}
while (state.KeepRunning()) {
auto data = resource.read_data();
if (!resource.good()) {
state.SkipWithError("Failed to read data!");
break; // Needed to skip the rest of the iteration.
}
do_stuff(data);
}
}
static void BM_test_ranged_fo(benchmark::State & state) {
auto resource = GetResource();
if (!resource.good()) {
state.SkipWithError("Resource is not good!");
return; // Early return is allowed when SkipWithError() has been used.
}
for (auto _ : state) {
auto data = resource.read_data();
if (!resource.good()) {
state.SkipWithError("Failed to read data!");
break; // REQUIRED to prevent all further iterations.
}
do_stuff(data);
}
}A Faster KeepRunning Loop
A ranged-based for loop should be used in preference to
the KeepRunning loop for running the benchmarks. For example:
static void BM_Fast(benchmark::State &state) {
for (auto _ : state) {
FastOperation();
}
}
BENCHMARK(BM_Fast);The reason the ranged-for loop is faster than using KeepRunning, is
because KeepRunning requires a memory load and store of the iteration count
ever iteration, whereas the ranged-for variant is able to keep the iteration count
in a register.
For example, an empty inner loop of using the ranged-based for method looks like:
# Loop Init
mov rbx, qword ptr [r14 + 104]
call benchmark::State::StartKeepRunning()
test rbx, rbx
je .LoopEnd
.LoopHeader: # =>This Inner Loop Header: Depth=1
add rbx, -1
jne .LoopHeader
.LoopEnd:Compared to an empty KeepRunning loop, which looks like:
.LoopHeader: # in Loop: Header=BB0_3 Depth=1
cmp byte ptr [rbx], 1
jne .LoopInit
.LoopBody: # =>This Inner Loop Header: Depth=1
mov rax, qword ptr [rbx + 8]
lea rcx, [rax + 1]
mov qword ptr [rbx + 8], rcx
cmp rax, qword ptr [rbx + 104]
jb .LoopHeader
jmp .LoopEnd
.LoopInit:
mov rdi, rbx
call benchmark::State::StartKeepRunning()
jmp .LoopBody
.LoopEnd:Unless C++03 compatibility is required, the ranged-for variant of writing
the benchmark loop should be preferred.
Disabling CPU Frequency Scaling
If you see this error:
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may
be noisy and will incur extra overhead.you might want to disable the CPU frequency scaling while running the
benchmark, as well as consider other ways to stabilize the performance of
your system while benchmarking.
See Reducing Variance for more information.
CONTRIBUTING
How to contribute
We'd love to accept your patches and contributions to this project. There are
a just a few small guidelines you need to follow.
Contributor License Agreement
Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright assignment, it simply gives
Google permission to use and redistribute your contributions as part of the
project.
If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an individual
CLA.If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a corporate CLA.
You generally only need to submit a CLA once, so if you've already submitted
one (even if it was for a different project), you probably don't need to do it
again.
Once your CLA is submitted (or if you already submitted one for
another Google project), make a commit adding yourself to the
AUTHORS and CONTRIBUTORS files. This commit can be part
of your first pull request.
Submitting a patch
It's generally best to start by opening a new issue describing the bug or
feature you're intending to fix. Even if you think it's relatively minor,
it's helpful to know what people are working on. Mention in the initial
issue that you are planning to work on that bug or feature so that it can
be assigned to you.Follow the normal process of forking the project, and setup a new
branch to work in. It's important that each group of changes be done in
separate branches in order to ensure that a pull request only includes the
commits related to that bug or feature.Do your best to have well-formed commit messages for each change.
This provides consistency throughout the project, and ensures that commit
messages are able to be formatted properly by various git tools.Finally, push the commits to your fork and submit a pull request.
AI usage
Please consult AGENTS file.
README
Benchmark
A library to benchmark code snippets, similar to unit tests. Example:
#include <benchmark/registration.h>
#include <benchmark/state.h>
static void BM_SomeFunction(benchmark::State& state) {
// Perform setup here
for (auto _ : state) {
// This code gets timed
SomeFunction();
}
}
// Register the function as a benchmark
BENCHMARK(BM_SomeFunction);
// Run the benchmark
BENCHMARK_MAIN();Getting Started
To get started, see Requirements and
Installation. See Usage for a full example and the
User Guide for a more comprehensive feature overview.
It may also help to read the Google Test documentation
as some of the structural aspects of the APIs are similar.
Resources
IRC channels:
- libera #benchmark
Additional Tooling Documentation
Assembly Testing Documentation
Building and installing Python bindings
Requirements
The library can be used with C++11. However, it requires C++17 to build,
including compiler and standard library support.
See dependencies.md for more details regarding supported
compilers and standards.
If you have need for a particular compiler to be supported, patches are very welcome.
See Platform-Specific Build Instructions.
Installation
This describes the installation process using cmake. As pre-requisites, you'll
need git and cmake installed.
See dependencies.md for more details regarding supported
versions of build tools.
# Check out the library.
$ git clone https://github.com/google/benchmark.git
# Go to the library root directory
$ cd benchmark
# Make a build directory to place the build output.
$ cmake -E make_directory "build"
# Generate build system files with cmake, and download any dependencies.
$ cmake -DBENCHMARK_DOWNLOAD_DEPENDENCIES=on -DCMAKE_BUILD_TYPE=Release -S . -B "build"
# Build the library.
$ cmake --build "build" --config ReleaseThis builds the benchmark and benchmark_main libraries and tests.
On a unix system, the build directory should now look something like this:
/benchmark
/build
/src
/libbenchmark.a
/libbenchmark_main.a
/test
...Next, you can run the tests to check the build.
$ cmake -E chdir "build" ctest --build-config ReleaseIf you want to install the library globally, also run:
sudo cmake --build "build" --config Release --target installNote that Google Benchmark requires Google Test to build and run the tests. This
dependency can be provided two ways:
- Checkout the Google Test sources into
benchmark/googletest. - Otherwise, if
-DBENCHMARK_DOWNLOAD_DEPENDENCIES=ONis specified during
configuration as above, the library will automatically download and build
any required dependencies.
If you do not wish to build and run the tests, add -DBENCHMARK_ENABLE_GTEST_TESTS=OFF
to CMAKE_ARGS.
Debug vs Release
By default, benchmark builds as a debug library. You will see a warning in the
output when this is the case. To build it as a release library instead, add-DCMAKE_BUILD_TYPE=Release when generating the build system files, as shown
above. The use of --config Release in build commands is needed to properly
support multi-configuration tools (like Visual Studio for example) and can be
skipped for other build systems (like Makefile).
To enable link-time optimisation, also add -DBENCHMARK_ENABLE_LTO=true when
generating the build system files.
If you are using gcc, you might need to set GCC_AR and GCC_RANLIB cmake
cache variables, if autodetection fails.
If you are using clang, you may need to set LLVMAR_EXECUTABLE,LLVMNM_EXECUTABLE and LLVMRANLIB_EXECUTABLE cmake cache variables.
To enable sanitizer checks (eg., asan and tsan), add:
-DCMAKE_C_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=address -fsanitize=thread -fno-sanitize-recover=all"
-DCMAKE_CXX_FLAGS="-g -O2 -fno-omit-frame-pointer -fsanitize=address -fsanitize=thread -fno-sanitize-recover=all "Stable and Experimental Library Versions
The main branch contains the latest stable version of the benchmarking library;
the API of which can be considered largely stable, with source breaking changes
being made only upon the release of a new major version.
Newer, experimental, features are implemented and tested on thev2 branch. Users who wish
to use, test, and provide feedback on the new features are encouraged to try
this branch. However, this branch provides no stability guarantees and reserves
the right to change and break the API at any time.
Usage
Basic usage
Define a function that executes the code to measure, register it as a benchmark
function using the BENCHMARK macro, and ensure an appropriate main function
is available:
#include <benchmark/benchmark.h>
static void BM_StringCreation(benchmark::State& state) {
for (auto _ : state)
std::string empty_string;
}
// Register the function as a benchmark
BENCHMARK(BM_StringCreation);
// Define another benchmark
static void BM_StringCopy(benchmark::State& state) {
std::string x = "hello";
for (auto _ : state)
std::string copy(x);
}
BENCHMARK(BM_StringCopy);
BENCHMARK_MAIN();To run the benchmark, compile and link against the benchmark library
(libbenchmark.a/.so). If you followed the build steps above, this library will
be under the build directory you created.
# Example on linux after running the build steps above. Assumes the
# `benchmark` and `build` directories are under the current directory.
$ g++ mybenchmark.cc -std=c++11 -isystem benchmark/include \
-Lbenchmark/build/src -lbenchmark -lpthread -o mybenchmarkAlternatively, link against the benchmark_main library and removeBENCHMARK_MAIN(); above to get the same behavior.
The compiled executable will run all benchmarks by default. Pass the --help
flag for option information or see the User Guide.
Usage with CMake
If using CMake, it is recommended to link against the project-providedbenchmark::benchmark or benchmark::benchmark_main targets usingtarget_link_libraries. Link to benchmark::benchmark when your target
defines its own main function, or link to benchmark::benchmark_main to use
the default benchmark entry point. The benchmark::benchmark_main target linksbenchmark::benchmark transitively.
It is possible to use find_package to import an installed version of the
library.
find_package(benchmark REQUIRED)Alternatively, add_subdirectory will incorporate the library directly in
to one's CMake project.
add_subdirectory(benchmark)Either way, link to the library as follows.
target_link_libraries(MyTarget benchmark::benchmark)
# Or, when you do not define your own main:
target_link_libraries(MyTarget benchmark::benchmark_main)When benchmark sources are shared through an intermediate CMake target, choose
an object library instead of a static library:
add_library(shared_benchmarks OBJECT bench.cc)
target_link_libraries(shared_benchmarks benchmark::benchmark_main)
add_executable(runnable_benchmarks)
target_link_libraries(runnable_benchmarks shared_benchmarks)This links the object file that contains BENCHMARK registrations into the
final executable. If those registrations are placed only in an intermediateSTATIC library, the linker may not copy static registration symbols, and thus
benchmarks will not be part of the final executable.
Embedding Google Benchmark in another CMake project
There are two common ways to consume Google Benchmark from a CMake project:
- Use an installed or package-managed copy, for example from a system package
manager or vcpkg, and import it withfind_package(benchmark REQUIRED). - Add this repository to the source tree, for example as a submodule or
FetchContentdependency, and calladd_subdirectory.
The installed form keeps Google Benchmark's build separate from the parent
project and is usually the simplest choice for system packages and vcpkg. The
source-tree form is useful when the parent project wants to pin a specific
commit or build Google Benchmark as part of its normal CMake configure step.
When embedding from source, most projects should turn off Google Benchmark's
tests and install rules:
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
add_subdirectory(third_party/benchmark)
target_link_libraries(MyTarget benchmark::benchmark)If Google Test is not already provided by the parent build, either check out the
Google Test sources under benchmark/googletest or configure withBENCHMARK_DOWNLOAD_DEPENDENCIES=ON. For projects that only link the benchmark
library and do not build Google Benchmark's tests, disablingBENCHMARK_ENABLE_GTEST_TESTS avoids the Google Test dependency.
Google Benchmark follows CMake's BUILD_SHARED_LIBS setting when selecting
static or shared library output. On Windows, keep this setting consistent with
the rest of the project and make sure the same runtime library configuration is
used across the benchmark library and the targets that link it.
Usage with Bazel
If using Bazel with Bzlmod, add Google Benchmark to your MODULE.bazel file:
bazel_dep(name = "google_benchmark", version = "<VERSION>")Replace <VERSION> with the Google Benchmark release version you want to use.
Then link a cc_binary or cc_test against one of the provided targets:
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "my_benchmark",
srcs = ["my_benchmark.cc"],
deps = ["@google_benchmark//:benchmark_main"],
)Use @google_benchmark//:benchmark when your target defines its own main
function, including through BENCHMARK_MAIN(). Use@google_benchmark//:benchmark_main to use the default Google Benchmark entry
point.
For WORKSPACE setup and more examples, see Bazel.