A Timer for Modern C++¶
core::timer::Timer
is a header-only timer for modern C++
. The
timer is designed for minimal-overhead, ad-hoc timing of C++
code
including micro-timing down to single machine instructions. The timer
is not designed for benchmarking
(benchmark or
nanobench are excellent tools
for this purpose) nor for detailed performance analysis.
Brief Tour¶
The timer has two basic modes:
Inovking
run
on isolated code for in vivo measurements.Using
start
andstop
timer calls for in vitro measurements.
In Vivo Example¶
The run
method is designed to repeatedly execute isolated code in
order to measure the average cost per operation. Modern compilers,
taking advantge of the C++
standard’s “as if” rule, will often
eliminate code that produces no visible side-effects making this type
of measurement difficult.
The timer library provides the core::timer::doNotOptimizeAway
function which forces the compiler to materialize the given expression
either in memory or in a register. The compiler is still allowed to
fully optimize the expression given that single constraint.
The following example demonstrates using the run
method coupled with
the doNotOptimizeAway
function to measure the cost of performing an
integer addition (i.e. output += 1
). Without the doNotOptimizeAway
call, a typical outcome is for the compiler to eliminate all of the
computation since there are no visible side-effects.
#include <iomanip>
#include <iostream>
#include "core/timer/timer.h"
using namespace core;
int main(int argc, const char *argv[]) {
unsigned int output{};
auto ns = timer::Timer().run(1'000'000, [&]() {
output += 1;
timer::doNotOptimizeAway(output);
}).elapsed_per_iteration();
std::cout << std::setprecision(2) << ns << " nanoseconds per operation" << std::endl;
// 0.31 nanoseconds per operation
return 0;
}
In Vitro Example¶
The start
and stop
methods can be used to instrument code as it
exists without isolating it to be executed by run
. On a modern
platform, each invocation of start
and stop
costs tens of clock
cycles, thus, they are designed to be applied to more significant code
segments.
The following example demonstrates using the start
and stop
methods to measure the cost of an exclusive or performed in a
loop. Referencing the output
variable at the end serves the same
function as the doNotOptimizeAway
function in the previous example;
it forces the compiler to actually do the computation.
#include <iostream>
#include "core/timer/timer.h"
using namespace core;
int main(int argc, const char *argv[]) {
timer::Timer timer;
timer.start();
unsigned int output{};
for (auto i = 0; i < 10'000; ++i)
output ^= i;
timer.stop();
auto ns = timer.elapsed().count();
std::cout << ns << " nanoseconds" << std::endl;
std::cout << output << std::endl;
// 3150 nanoseconds
return 0;
}
Installation¶
git clone https://github.com/cpp-core/pp
mkdir pp/build && cd pp/build
CC=clang-mp-14 CXX=clang++-mp-14 cmake -DCMAKE_INSTALL_PREFIX=$HOME/opt ..
make -j4 check # Run tests
make install # Do the install
To build the documentation (requires doxygen and sphinx-build to be installed)::
CC=clang-11 CXX=clang++11 cmake -DCORE_DOCS ..
make cxx_core_pp_docs # root of html tree is docs/html/index.html
Background¶
Timer is part of the cpp-core* family of C++
libraries. The
cpp-core libraries have two primary goals:
Ergonomics. In the spirit of the
CppCoreGuideLines
, cpp-core aspires to facilitate writing concise, idiomatic code not by force, but by providing a clear path that produces robust, easy to read code. Good code should be fun to write.Components. Modern language platforms provide broad support for first-class library components, while C++ has traditionally only provided a minimal standard library. cpp-core aspires to provide support for a broad range of libraries that feel like part of the language.
License¶
This software is licensed under the BSD 3-clause license. See the LICENSE file for details.