nanobench.h raw
1 // __ _ _______ __ _ _____ ______ _______ __ _ _______ _ _
2 // | \ | |_____| | \ | | | |_____] |______ | \ | | |_____|
3 // | \_| | | | \_| |_____| |_____] |______ | \_| |_____ | |
4 //
5 // Microbenchmark framework for C++11/14/17/20
6 // https://github.com/martinus/nanobench
7 //
8 // Licensed under the MIT License <http://opensource.org/licenses/MIT>.
9 // SPDX-License-Identifier: MIT
10 // Copyright (c) 2019-2023 Martin Leitner-Ankerl <martin.ankerl@gmail.com>
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining a copy
13 // of this software and associated documentation files (the "Software"), to deal
14 // in the Software without restriction, including without limitation the rights
15 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 // copies of the Software, and to permit persons to whom the Software is
17 // furnished to do so, subject to the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be included in all
20 // copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 // SOFTWARE.
29
30 #ifndef ANKERL_NANOBENCH_H_INCLUDED
31 #define ANKERL_NANOBENCH_H_INCLUDED
32
33 // see https://semver.org/
34 #define ANKERL_NANOBENCH_VERSION_MAJOR 4 // incompatible API changes
35 #define ANKERL_NANOBENCH_VERSION_MINOR 3 // backwards-compatible changes
36 #define ANKERL_NANOBENCH_VERSION_PATCH 11 // backwards-compatible bug fixes
37
38 ///////////////////////////////////////////////////////////////////////////////////////////////////
39 // public facing api - as minimal as possible
40 ///////////////////////////////////////////////////////////////////////////////////////////////////
41
42 #include <chrono> // high_resolution_clock
43 #include <cassert> // assert
44 #include <cstdint>
45 #include <cstring> // memcpy
46 // IWYU will only see this header with ANKERL_NANOBENCH_IMPLEMENT defined, which
47 // makes it suggest removing forward declarations for the Input/output library.
48 // IWYU pragma: begin_keep
49 #include <iosfwd> // for std::ostream* custom output target in Config
50 // IWYU pragma: end_keep
51 #include <limits>
52 #include <ratio>
53 #include <string> // all names
54 #include <type_traits>
55 #include <unordered_map> // holds context information of results
56 #include <utility>
57 #include <vector> // holds all results
58
59 #define ANKERL_NANOBENCH(x) ANKERL_NANOBENCH_PRIVATE_##x()
60
61 #define ANKERL_NANOBENCH_PRIVATE_CXX() __cplusplus
62 #define ANKERL_NANOBENCH_PRIVATE_CXX98() 199711L
63 #define ANKERL_NANOBENCH_PRIVATE_CXX11() 201103L
64 #define ANKERL_NANOBENCH_PRIVATE_CXX14() 201402L
65 #define ANKERL_NANOBENCH_PRIVATE_CXX17() 201703L
66
67 #if ANKERL_NANOBENCH(CXX) >= ANKERL_NANOBENCH(CXX17)
68 # define ANKERL_NANOBENCH_PRIVATE_NODISCARD() [[nodiscard]]
69 #else
70 # define ANKERL_NANOBENCH_PRIVATE_NODISCARD()
71 #endif
72
73 #if defined(__clang__)
74 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH() \
75 _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpadded\"")
76 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP() _Pragma("clang diagnostic pop")
77 #else
78 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH()
79 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP()
80 #endif
81
82 #if defined(__GNUC__)
83 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH() _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Weffc++\"")
84 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP() _Pragma("GCC diagnostic pop")
85 #else
86 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH()
87 # define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP()
88 #endif
89
90 #if defined(ANKERL_NANOBENCH_LOG_ENABLED)
91 # include <iostream>
92 # define ANKERL_NANOBENCH_LOG(x) \
93 do { \
94 std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl; \
95 } while (0)
96 #else
97 # define ANKERL_NANOBENCH_LOG(x) \
98 do { \
99 } while (0)
100 #endif
101
102 #define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 0
103 #if defined(__linux__) && !defined(ANKERL_NANOBENCH_DISABLE_PERF_COUNTERS)
104 # include <linux/version.h>
105 # if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)
106 // PERF_COUNT_HW_REF_CPU_CYCLES only available since kernel 3.3
107 // PERF_FLAG_FD_CLOEXEC since kernel 3.14
108 # undef ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS
109 # define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 1
110 # endif
111 #endif
112
113 #if defined(__clang__)
114 # define ANKERL_NANOBENCH_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__)))
115 #else
116 # define ANKERL_NANOBENCH_NO_SANITIZE(...)
117 #endif
118
119 #if defined(_MSC_VER)
120 # define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __declspec(noinline)
121 #else
122 # define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __attribute__((noinline))
123 #endif
124
125 // workaround missing "is_trivially_copyable" in g++ < 5.0
126 // See https://stackoverflow.com/a/31798726/48181
127 #if defined(__GNUC__) && __GNUC__ < 5
128 # define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__)
129 #else
130 # define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value
131 #endif
132
133 // noexcept may be missing for std::string.
134 // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58265
135 #define ANKERL_NANOBENCH_PRIVATE_NOEXCEPT_STRING_MOVE() std::is_nothrow_move_assignable<std::string>::value
136
137 // declarations ///////////////////////////////////////////////////////////////////////////////////
138
139 // As definitions follow these declarations within this
140 // header, IWYU considers some of them redundant and
141 // suggests removing them. Disable this IWYU behavior.
142 // IWYU pragma: begin_keep
143
144 namespace ankerl {
145 namespace nanobench {
146
147 using Clock = std::conditional<std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock,
148 std::chrono::steady_clock>::type;
149 class Bench;
150 struct Config;
151 class Result;
152 class Rng;
153 class BigO;
154
155 namespace detail {
156 template <typename SetupOp>
157 class SetupRunner;
158 } // namespace detail
159
160 /**
161 * @brief Renders output from a mustache-like template and benchmark results.
162 *
163 * The templating facility here is heavily inspired by [mustache - logic-less templates](https://mustache.github.io/).
164 * It adds a few more features that are necessary to get all of the captured data out of nanobench. Please read the
165 * excellent [mustache manual](https://mustache.github.io/mustache.5.html) to see what this is all about.
166 *
167 * nanobench output has two nested layers, *result* and *measurement*. Here is a hierarchy of the allowed tags:
168 *
169 * * `{{#result}}` Marks the begin of the result layer. Whatever comes after this will be instantiated as often as
170 * a benchmark result is available. Within it, you can use these tags:
171 *
172 * * `{{title}}` See Bench::title.
173 *
174 * * `{{name}}` Benchmark name, usually directly provided with Bench::run, but can also be set with Bench::name.
175 *
176 * * `{{unit}}` Unit, e.g. `byte`. Defaults to `op`, see Bench::unit.
177 *
178 * * `{{batch}}` Batch size, see Bench::batch.
179 *
180 * * `{{complexityN}}` Value used for asymptotic complexity calculation. See Bench::complexityN.
181 *
182 * * `{{epochs}}` Number of epochs, see Bench::epochs.
183 *
184 * * `{{clockResolution}}` Accuracy of the clock, i.e. what's the smallest time possible to measure with the clock.
185 * For modern systems, this can be around 20 ns. This value is automatically determined by nanobench at the first
186 * benchmark that is run, and used as a static variable throughout the application's runtime.
187 *
188 * * `{{clockResolutionMultiple}}` Configuration multiplier for `clockResolution`. See Bench::clockResolutionMultiple.
189 * This is the target runtime for each measurement (epoch). That means the more accurate your clock is, the faster
190 * will be the benchmark. Basing the measurement's runtime on the clock resolution is the main reason why nanobench is so fast.
191 *
192 * * `{{maxEpochTime}}` Configuration for a maximum time each measurement (epoch) is allowed to take. Note that at least
193 * a single iteration will be performed, even when that takes longer than maxEpochTime. See Bench::maxEpochTime.
194 *
195 * * `{{minEpochTime}}` Minimum epoch time, defaults to 1ms. See Bench::minEpochTime.
196 *
197 * * `{{minEpochIterations}}` See Bench::minEpochIterations.
198 *
199 * * `{{epochIterations}}` See Bench::epochIterations.
200 *
201 * * `{{warmup}}` Number of iterations used before measuring starts. See Bench::warmup.
202 *
203 * * `{{relative}}` True or false, depending on the setting you have used. See Bench::relative.
204 *
205 * * `{{context(variableName)}}` See Bench::context.
206 *
207 * Apart from these tags, it is also possible to use some mathematical operations on the measurement data. The operations
208 * are of the form `{{command(name)}}`. Currently `name` can be one of `elapsed`, `iterations`. If performance counters
209 * are available (currently only on current Linux systems), you also have `pagefaults`, `cpucycles`,
210 * `contextswitches`, `instructions`, `branchinstructions`, and `branchmisses`. All the measures (except `iterations`) are
211 * provided for a single iteration (so `elapsed` is the time a single iteration took). The following tags are available:
212 *
213 * * `{{median(<name>)}}` Calculate median of a measurement data set, e.g. `{{median(elapsed)}}`.
214 *
215 * * `{{average(<name>)}}` Average (mean) calculation.
216 *
217 * * `{{medianAbsolutePercentError(<name>)}}` Calculates MdAPE, the Median Absolute Percentage Error. The MdAPE is an excellent
218 * metric for the variation of measurements. It is more robust to outliers than the
219 * [Mean absolute percentage error (M-APE)](https://en.wikipedia.org/wiki/Mean_absolute_percentage_error).
220 * @f[
221 * \mathrm{MdAPE}(e) = \mathrm{med}\{| \frac{e_i - \mathrm{med}\{e\}}{e_i}| \}
222 * @f]
223 * E.g. for *elapsed*: First, @f$ \mathrm{med}\{e\} @f$ calculates the median by sorting and then taking the middle element
224 * of all *elapsed* measurements. This is used to calculate the absolute percentage
225 * error to this median for each measurement, as in @f$ | \frac{e_i - \mathrm{med}\{e\}}{e_i}| @f$. All these results
226 * are sorted, and the middle value is chosen as the median absolute percent error.
227 *
228 * This measurement is a bit hard to interpret, but it is very robust against outliers. E.g. a value of 5% means that half of the
229 * measurements deviate less than 5% from the median, and the other deviate more than 5% from the median.
230 *
231 * * `{{sum(<name>)}}` Sum of all the measurements. E.g. `{{sum(iterations)}}` will give you the total number of iterations
232 * measured in this benchmark.
233 *
234 * * `{{minimum(<name>)}}` Minimum of all measurements.
235 *
236 * * `{{maximum(<name>)}}` Maximum of all measurements.
237 *
238 * * `{{sumProduct(<first>, <second>)}}` Calculates the sum of the products of corresponding measures:
239 * @f[
240 * \mathrm{sumProduct}(a,b) = \sum_{i=1}^{n}a_i\cdot b_i
241 * @f]
242 * E.g. to calculate total runtime of the benchmark, you multiply iterations with elapsed time for each measurement, and
243 * sum these results up:
244 * `{{sumProduct(iterations, elapsed)}}`.
245 *
246 * * `{{#measurement}}` To access individual measurement results, open the begin tag for measurements.
247 *
248 * * `{{elapsed}}` Average elapsed wall clock time per iteration, in seconds.
249 *
250 * * `{{iterations}}` Number of iterations in the measurement. The number of iterations will fluctuate due
251 * to some applied randomness, to enhance accuracy.
252 *
253 * * `{{pagefaults}}` Average number of pagefaults per iteration.
254 *
255 * * `{{cpucycles}}` Average number of CPU cycles processed per iteration.
256 *
257 * * `{{contextswitches}}` Average number of context switches per iteration.
258 *
259 * * `{{instructions}}` Average number of retired instructions per iteration.
260 *
261 * * `{{branchinstructions}}` Average number of branches executed per iteration.
262 *
263 * * `{{branchmisses}}` Average number of branches that were missed per iteration.
264 *
265 * * `{{/measurement}}` Ends the measurement tag.
266 *
267 * * `{{/result}}` Marks the end of the result layer. This is the end marker for the template part that will be instantiated
268 * for each benchmark result.
269 *
270 *
271 * For the layer tags *result* and *measurement* you additionally can use these special markers:
272 *
273 * * ``{{#-first}}`` - Begin marker of a template that will be instantiated *only for the first* entry in the layer. Use is only
274 * allowed between the begin and end marker of the layer. So between ``{{#result}}`` and ``{{/result}}``, or between
275 * ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-first}}``.
276 *
277 * * ``{{^-first}}`` - Begin marker of a template that will be instantiated *for each except the first* entry in the layer. This,
278 * this is basically the inversion of ``{{#-first}}``. Use is only allowed between the begin and end marker of the layer.
279 * So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``.
280 *
281 * * ``{{/-first}}`` - End marker for either ``{{#-first}}`` or ``{{^-first}}``.
282 *
283 * * ``{{#-last}}`` - Begin marker of a template that will be instantiated *only for the last* entry in the layer. Use is only
284 * allowed between the begin and end marker of the layer. So between ``{{#result}}`` and ``{{/result}}``, or between
285 * ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-last}}``.
286 *
287 * * ``{{^-last}}`` - Begin marker of a template that will be instantiated *for each except the last* entry in the layer. This,
288 * this is basically the inversion of ``{{#-last}}``. Use is only allowed between the begin and end marker of the layer.
289 * So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``.
290 *
291 * * ``{{/-last}}`` - End marker for either ``{{#-last}}`` or ``{{^-last}}``.
292 *
293 @verbatim embed:rst
294
295 For an overview of all the possible data you can get out of nanobench, please see the tutorial at :ref:`tutorial-template-json`.
296
297 The templates that ship with nanobench are:
298
299 * :cpp:func:`templates::csv() <ankerl::nanobench::templates::csv()>`
300 * :cpp:func:`templates::json() <ankerl::nanobench::templates::json()>`
301 * :cpp:func:`templates::htmlBoxplot() <ankerl::nanobench::templates::htmlBoxplot()>`
302 * :cpp:func:`templates::pyperf() <ankerl::nanobench::templates::pyperf()>`
303
304 @endverbatim
305 *
306 * @param mustacheTemplate The template.
307 * @param bench Benchmark, containing all the results.
308 * @param out Output for the generated output.
309 */
310 void render(char const* mustacheTemplate, Bench const& bench, std::ostream& out);
311 void render(std::string const& mustacheTemplate, Bench const& bench, std::ostream& out);
312
313 /**
314 * Same as render(char const* mustacheTemplate, Bench const& bench, std::ostream& out), but for when
315 * you only have results available.
316 *
317 * @param mustacheTemplate The template.
318 * @param results All the results to be used for rendering.
319 * @param out Output for the generated output.
320 */
321 void render(char const* mustacheTemplate, std::vector<Result> const& results, std::ostream& out);
322 void render(std::string const& mustacheTemplate, std::vector<Result> const& results, std::ostream& out);
323
324 // Contains mustache-like templates
325 namespace templates {
326
327 /*!
328 @brief CSV data for the benchmark results.
329
330 Generates a comma-separated values dataset. First line is the header, each following line is a summary of each benchmark run.
331
332 @verbatim embed:rst
333 See the tutorial at :ref:`tutorial-template-csv` for an example.
334 @endverbatim
335 */
336 char const* csv() noexcept;
337
338 /*!
339 @brief HTML output that uses plotly to generate an interactive boxplot chart. See the tutorial for an example output.
340
341 The output uses only the elapsed wall clock time, and displays each epoch as a single dot.
342 @verbatim embed:rst
343 See the tutorial at :ref:`tutorial-template-html` for an example.
344 @endverbatim
345
346 @see also ankerl::nanobench::render()
347 */
348 char const* htmlBoxplot() noexcept;
349
350 /*!
351 @brief Output in pyperf compatible JSON format, which can be used for more analyzation.
352 @verbatim embed:rst
353 See the tutorial at :ref:`tutorial-template-pyperf` for an example how to further analyze the output.
354 @endverbatim
355 */
356 char const* pyperf() noexcept;
357
358 /*!
359 @brief Template to generate JSON data.
360
361 The generated JSON data contains *all* data that has been generated. All times are as double values, in seconds. The output can get
362 quite large.
363 @verbatim embed:rst
364 See the tutorial at :ref:`tutorial-template-json` for an example.
365 @endverbatim
366 */
367 char const* json() noexcept;
368
369 } // namespace templates
370
371 namespace detail {
372
373 template <typename T>
374 struct PerfCountSet;
375
376 class IterationLogic;
377 class PerformanceCounters;
378
379 #if ANKERL_NANOBENCH(PERF_COUNTERS)
380 class LinuxPerformanceCounters;
381 #endif
382
383 } // namespace detail
384 } // namespace nanobench
385 } // namespace ankerl
386
387 // IWYU pragma: end_keep
388
389 // definitions ////////////////////////////////////////////////////////////////////////////////////
390
391 namespace ankerl {
392 namespace nanobench {
393 namespace detail {
394
395 template <typename T>
396 struct PerfCountSet {
397 T pageFaults{};
398 T cpuCycles{};
399 T contextSwitches{};
400 T instructions{};
401 T branchInstructions{};
402 T branchMisses{};
403 };
404
405 } // namespace detail
406
407 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
408 struct Config {
409 // actual benchmark config
410 std::string mBenchmarkTitle = "benchmark"; // NOLINT(misc-non-private-member-variables-in-classes)
411 std::string mBenchmarkName = "noname"; // NOLINT(misc-non-private-member-variables-in-classes)
412 std::string mUnit = "op"; // NOLINT(misc-non-private-member-variables-in-classes)
413 double mBatch = 1.0; // NOLINT(misc-non-private-member-variables-in-classes)
414 double mComplexityN = -1.0; // NOLINT(misc-non-private-member-variables-in-classes)
415 size_t mNumEpochs = 11; // NOLINT(misc-non-private-member-variables-in-classes)
416 size_t mClockResolutionMultiple = static_cast<size_t>(1000); // NOLINT(misc-non-private-member-variables-in-classes)
417 std::chrono::nanoseconds mMaxEpochTime = std::chrono::milliseconds(100); // NOLINT(misc-non-private-member-variables-in-classes)
418 std::chrono::nanoseconds mMinEpochTime = std::chrono::milliseconds(1); // NOLINT(misc-non-private-member-variables-in-classes)
419 uint64_t mMinEpochIterations{1}; // NOLINT(misc-non-private-member-variables-in-classes)
420 // If not 0, run *exactly* these number of iterations per epoch.
421 uint64_t mEpochIterations{0}; // NOLINT(misc-non-private-member-variables-in-classes)
422 uint64_t mWarmup = 0; // NOLINT(misc-non-private-member-variables-in-classes)
423 std::ostream* mOut = nullptr; // NOLINT(misc-non-private-member-variables-in-classes)
424 std::chrono::duration<double> mTimeUnit = std::chrono::nanoseconds{1}; // NOLINT(misc-non-private-member-variables-in-classes)
425 std::string mTimeUnitName = "ns"; // NOLINT(misc-non-private-member-variables-in-classes)
426 bool mShowPerformanceCounters = true; // NOLINT(misc-non-private-member-variables-in-classes)
427 bool mIsRelative = false; // NOLINT(misc-non-private-member-variables-in-classes)
428 std::unordered_map<std::string, std::string> mContext{}; // NOLINT(misc-non-private-member-variables-in-classes)
429
430 Config();
431 ~Config();
432 Config& operator=(Config const& other);
433 Config& operator=(Config&& other) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE));
434 Config(Config const& other);
435 Config(Config&& other) noexcept;
436 };
437 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
438
439 // Result returned after a benchmark has finished. Can be used as a baseline for relative().
440 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
441 class Result {
442 public:
443 enum class Measure : size_t {
444 elapsed,
445 iterations,
446 pagefaults,
447 cpucycles,
448 contextswitches,
449 instructions,
450 branchinstructions,
451 branchmisses,
452 _size
453 };
454
455 explicit Result(Config benchmarkConfig);
456
457 ~Result();
458 Result& operator=(Result const& other);
459 Result& operator=(Result&& other) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE));
460 Result(Result const& other);
461 Result(Result&& other) noexcept;
462
463 // adds new measurement results
464 // all values are scaled by iters (except iters...)
465 void add(Clock::duration totalElapsed, uint64_t iters, detail::PerformanceCounters const& pc);
466
467 ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept;
468
469 ANKERL_NANOBENCH(NODISCARD) double median(Measure m) const;
470 ANKERL_NANOBENCH(NODISCARD) double medianAbsolutePercentError(Measure m) const;
471 ANKERL_NANOBENCH(NODISCARD) double average(Measure m) const;
472 ANKERL_NANOBENCH(NODISCARD) double sum(Measure m) const noexcept;
473 ANKERL_NANOBENCH(NODISCARD) double sumProduct(Measure m1, Measure m2) const noexcept;
474 ANKERL_NANOBENCH(NODISCARD) double minimum(Measure m) const noexcept;
475 ANKERL_NANOBENCH(NODISCARD) double maximum(Measure m) const noexcept;
476 ANKERL_NANOBENCH(NODISCARD) std::string const& context(char const* variableName) const;
477 ANKERL_NANOBENCH(NODISCARD) std::string const& context(std::string const& variableName) const;
478
479 ANKERL_NANOBENCH(NODISCARD) bool has(Measure m) const noexcept;
480 ANKERL_NANOBENCH(NODISCARD) double get(size_t idx, Measure m) const;
481 ANKERL_NANOBENCH(NODISCARD) bool empty() const noexcept;
482 ANKERL_NANOBENCH(NODISCARD) size_t size() const noexcept;
483
484 // Finds string, if not found, returns _size.
485 static Measure fromString(std::string const& str);
486
487 private:
488 Config mConfig{};
489 std::vector<std::vector<double>> mNameToMeasurements{};
490 };
491 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
492
493 /**
494 * An extremely fast random generator. Currently, this implements *RomuDuoJr*, developed by Mark Overton. Source:
495 * http://www.romu-random.org/
496 *
497 * RomuDuoJr is extremely fast and provides reasonable good randomness. Not enough for large jobs, but definitely
498 * good enough for a benchmarking framework.
499 *
500 * * Estimated capacity: @f$ 2^{51} @f$ bytes
501 * * Register pressure: 4
502 * * State size: 128 bits
503 *
504 * This random generator is a drop-in replacement for the generators supplied by ``<random>``. It is not
505 * cryptographically secure. It's intended purpose is to be very fast so that benchmarks that make use
506 * of randomness are not distorted too much by the random generator.
507 *
508 * Rng also provides a few non-standard helpers, optimized for speed.
509 */
510 class Rng final {
511 public:
512 /**
513 * @brief This RNG provides 64bit randomness.
514 */
515 using result_type = uint64_t;
516
517 static constexpr uint64_t(min)();
518 static constexpr uint64_t(max)();
519
520 /**
521 * As a safety precaution, we don't allow copying. Copying a PRNG would mean you would have two random generators that produce the
522 * same sequence, which is generally not what one wants. Instead create a new rng with the default constructor Rng(), which is
523 * automatically seeded from `std::random_device`. If you really need a copy, use `copy()`.
524 */
525 Rng(Rng const&) = delete;
526
527 /**
528 * Same as Rng(Rng const&), we don't allow assignment. If you need a new Rng create one with the default constructor Rng().
529 */
530 Rng& operator=(Rng const&) = delete;
531
532 // moving is ok
533 Rng(Rng&&) noexcept = default;
534 Rng& operator=(Rng&&) noexcept = default;
535 ~Rng() noexcept = default;
536
537 /**
538 * @brief Creates a new Random generator with random seed.
539 *
540 * Instead of a default seed (as the random generators from the STD), this properly seeds the random generator from
541 * `std::random_device`. It guarantees correct seeding. Note that seeding can be relatively slow, depending on the source of
542 * randomness used. So it is best to create a Rng once and use it for all your randomness purposes.
543 */
544 Rng();
545
546 /*!
547 Creates a new Rng that is seeded with a specific seed. Each Rng created from the same seed will produce the same randomness
548 sequence. This can be useful for deterministic behavior.
549
550 @verbatim embed:rst
551 .. note::
552
553 The random algorithm might change between nanobench releases. Whenever a faster and/or better random
554 generator becomes available, I will switch the implementation.
555 @endverbatim
556
557 As per the Romu paper, this seeds the Rng with splitMix64 algorithm and performs 10 initial rounds for further mixing up of the
558 internal state.
559
560 @param seed The 64bit seed. All values are allowed, even 0.
561 */
562 explicit Rng(uint64_t seed) noexcept;
563 Rng(uint64_t x, uint64_t y) noexcept;
564 explicit Rng(std::vector<uint64_t> const& data);
565
566 /**
567 * Creates a copy of the Rng, thus the copy provides exactly the same random sequence as the original.
568 */
569 ANKERL_NANOBENCH(NODISCARD) Rng copy() const noexcept;
570
571 /**
572 * @brief Produces a 64bit random value. This should be very fast, thus it is marked as inline. In my benchmark, this is ~46 times
573 * faster than `std::default_random_engine` for producing 64bit random values. It seems that the fastest std contender is
574 * `std::mt19937_64`. Still, this RNG is 2-3 times as fast.
575 *
576 * @return uint64_t The next 64 bit random value.
577 */
578 inline uint64_t operator()() noexcept;
579
580 // This is slightly biased. See
581
582 /**
583 * Generates a random number between 0 and range (excluding range).
584 *
585 * The algorithm only produces 32bit numbers, and is slightly biased. The effect is quite small unless your range is close to the
586 * maximum value of an integer. It is possible to correct the bias with rejection sampling (see
587 * [here](https://lemire.me/blog/2016/06/30/fast-random-shuffling/), but this is most likely irrelevant in practices for the
588 * purposes of this Rng.
589 *
590 * See Daniel Lemire's blog post [A fast alternative to the modulo
591 * reduction](https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/)
592 *
593 * @param range Upper exclusive range. E.g a value of 3 will generate random numbers 0, 1, 2.
594 * @return uint32_t Generated random values in range [0, range(.
595 */
596 inline uint32_t bounded(uint32_t range) noexcept;
597
598 // random double in range [0, 1(
599 // see http://prng.di.unimi.it/
600
601 /**
602 * Provides a random uniform double value between 0 and 1. This uses the method described in [Generating uniform doubles in the
603 * unit interval](http://prng.di.unimi.it/), and is extremely fast.
604 *
605 * @return double Uniformly distributed double value in range [0,1(, excluding 1.
606 */
607 inline double uniform01() noexcept;
608
609 /**
610 * Shuffles all entries in the given container. Although this has a slight bias due to the implementation of bounded(), this is
611 * preferable to `std::shuffle` because it is over 5 times faster. See Daniel Lemire's blog post [Fast random
612 * shuffling](https://lemire.me/blog/2016/06/30/fast-random-shuffling/).
613 *
614 * @param container The whole container will be shuffled.
615 */
616 template <typename Container>
617 void shuffle(Container& container) noexcept;
618
619 /**
620 * Extracts the full state of the generator, e.g. for serialization. For this RNG this is just 2 values, but to stay API compatible
621 * with future implementations that potentially use more state, we use a vector.
622 *
623 * @return Vector containing the full state:
624 */
625 ANKERL_NANOBENCH(NODISCARD) std::vector<uint64_t> state() const;
626
627 private:
628 static constexpr uint64_t rotl(uint64_t x, unsigned k) noexcept;
629
630 uint64_t mX;
631 uint64_t mY;
632 };
633
634 /**
635 * @brief Main entry point to nanobench's benchmarking facility.
636 *
637 * It holds configuration and results from one or more benchmark runs. Usually it is used in a single line, where the object is
638 * constructed, configured, and then a benchmark is run. E.g. like this:
639 *
640 * ankerl::nanobench::Bench().unit("byte").batch(1000).run("random fluctuations", [&] {
641 * // here be the benchmark code
642 * });
643 *
644 * In that example Bench() constructs the benchmark, it is then configured with unit() and batch(), and after configuration a
645 * benchmark is executed with run(). Once run() has finished, it prints the result to `std::cout`. It would also store the results
646 * in the Bench instance, but in this case the object is immediately destroyed so it's not available any more.
647 */
648 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
649 class Bench {
650 public:
651 /**
652 * @brief Creates a new benchmark for configuration and running of benchmarks.
653 */
654 Bench();
655
656 Bench(Bench&& other) noexcept;
657 Bench& operator=(Bench&& other) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE));
658 Bench(Bench const& other);
659 Bench& operator=(Bench const& other);
660 ~Bench() noexcept;
661
662 /*!
663 @brief Repeatedly calls `op()` based on the configuration, and performs measurements.
664
665 This call is marked with `noinline` to prevent the compiler to optimize beyond different benchmarks. This can have quite a big
666 effect on benchmark accuracy.
667
668 @verbatim embed:rst
669 .. note::
670
671 Each call to your lambda must have a side effect that the compiler can't possibly optimize it away. E.g. add a result to an
672 externally defined number (like `x` in the above example), and finally call `doNotOptimizeAway` on the variables the compiler
673 must not remove. You can also use :cpp:func:`ankerl::nanobench::doNotOptimizeAway` directly in the lambda, but be aware that
674 this has a small overhead.
675
676 @endverbatim
677
678 @tparam Op The code to benchmark.
679 */
680 template <typename Op>
681 ANKERL_NANOBENCH(NOINLINE)
682 Bench& run(char const* benchmarkName, Op&& op);
683
684 template <typename Op>
685 ANKERL_NANOBENCH(NOINLINE)
686 Bench& run(std::string const& benchmarkName, Op&& op);
687
688 /**
689 * @brief Same as run(char const* benchmarkName, Op op), but instead uses the previously set name.
690 * @tparam Op The code to benchmark.
691 */
692 template <typename Op>
693 ANKERL_NANOBENCH(NOINLINE)
694 Bench& run(Op&& op);
695
696 /**
697 * @brief Title of the benchmark, will be shown in the table header. Changing the title will start a new markdown table.
698 *
699 * @param benchmarkTitle The title of the benchmark.
700 */
701 Bench& title(char const* benchmarkTitle);
702 Bench& title(std::string const& benchmarkTitle);
703
704 /**
705 * @brief Gets the title of the benchmark
706 */
707 ANKERL_NANOBENCH(NODISCARD) std::string const& title() const noexcept;
708
709 /// Name of the benchmark, will be shown in the table row.
710 Bench& name(char const* benchmarkName);
711 Bench& name(std::string const& benchmarkName);
712 ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept;
713
714 /**
715 * @brief Set context information.
716 *
717 * The information can be accessed using custom render templates via `{{context(variableName)}}`.
718 * Trying to render a variable that hasn't been set before raises an exception.
719 * Not included in (default) markdown table.
720 *
721 * @see clearContext, render
722 *
723 * @param variableName The name of the context variable.
724 * @param variableValue The value of the context variable.
725 */
726 Bench& context(char const* variableName, char const* variableValue);
727 Bench& context(std::string const& variableName, std::string const& variableValue);
728
729 /**
730 * @brief Reset context information.
731 *
732 * This may improve efficiency when using many context entries,
733 * or improve robustness by removing spurious context entries.
734 *
735 * @see context
736 */
737 Bench& clearContext();
738
739 /**
740 * @brief Sets the batch size.
741 *
742 * E.g. number of processed byte, or some other metric for the size of the processed data in each iteration. If you benchmark
743 * hashing of a 1000 byte long string and want byte/sec as a result, you can specify 1000 as the batch size.
744 *
745 * @tparam T Any input type is internally cast to `double`.
746 * @param b batch size
747 */
748 template <typename T>
749 Bench& batch(T b) noexcept;
750 ANKERL_NANOBENCH(NODISCARD) double batch() const noexcept;
751
752 /**
753 * @brief Sets the operation unit.
754 *
755 * Defaults to "op". Could be e.g. "byte" for string processing. This is used for the table header, e.g. to show `ns/byte`. Use
756 * singular (*byte*, not *bytes*). A change clears the currently collected results.
757 *
758 * @param unit The unit name.
759 */
760 Bench& unit(char const* unit);
761 Bench& unit(std::string const& unit);
762 ANKERL_NANOBENCH(NODISCARD) std::string const& unit() const noexcept;
763
764 /**
765 * @brief Sets the time unit to be used for the default output.
766 *
767 * Nanobench defaults to using ns (nanoseconds) as output in the markdown. For some benchmarks this is too coarse, so it is
768 * possible to configure this. E.g. use `timeUnit(1ms, "ms")` to show `ms/op` instead of `ns/op`.
769 *
770 * @param tu Time unit to display the results in, default is 1ns.
771 * @param tuName Name for the time unit, default is "ns"
772 */
773 Bench& timeUnit(std::chrono::duration<double> const& tu, std::string const& tuName);
774 ANKERL_NANOBENCH(NODISCARD) std::string const& timeUnitName() const noexcept;
775 ANKERL_NANOBENCH(NODISCARD) std::chrono::duration<double> const& timeUnit() const noexcept;
776
777 /**
778 * @brief Set the output stream where the resulting markdown table will be printed to.
779 *
780 * The default is `&std::cout`. You can disable all output by setting `nullptr`.
781 *
782 * @param outstream Pointer to output stream, can be `nullptr`.
783 */
784 Bench& output(std::ostream* outstream) noexcept;
785 ANKERL_NANOBENCH(NODISCARD) std::ostream* output() const noexcept;
786
787 /**
788 * Modern processors have a very accurate clock, being able to measure as low as 20 nanoseconds. This is the main trick nanobech to
789 * be so fast: we find out how accurate the clock is, then run the benchmark only so often that the clock's accuracy is good enough
790 * for accurate measurements.
791 *
792 * The default is to run one epoch for 1000 times the clock resolution. So for 20ns resolution and 11 epochs, this gives a total
793 * runtime of
794 *
795 * @f[
796 * 20ns * 1000 * 11 \approx 0.2ms
797 * @f]
798 *
799 * To be precise, nanobench adds a 0-20% random noise to each evaluation. This is to prevent any aliasing effects, and further
800 * improves accuracy.
801 *
802 * Total runtime will be higher though: Some initial time is needed to find out the target number of iterations for each epoch, and
803 * there is some overhead involved to start & stop timers and calculate resulting statistics and writing the output.
804 *
805 * @param multiple Target number of times of clock resolution. Usually 1000 is a good compromise between runtime and accuracy.
806 */
807 Bench& clockResolutionMultiple(size_t multiple) noexcept;
808 ANKERL_NANOBENCH(NODISCARD) size_t clockResolutionMultiple() const noexcept;
809
810 /**
811 * @brief Controls number of epochs, the number of measurements to perform.
812 *
813 * The reported result will be the median of evaluation of each epoch. The higher you choose this, the more
814 * deterministic the result be and outliers will be more easily removed. Also the `err%` will be more accurate the higher this
815 * number is. Note that the `err%` will not necessarily decrease when number of epochs is increased. But it will be a more accurate
816 * representation of the benchmarked code's runtime stability.
817 *
818 * Choose the value wisely. In practice, 11 has been shown to be a reasonable choice between runtime performance and accuracy.
819 * This setting goes hand in hand with minEpochIterations() (or minEpochTime()). If you are more interested in *median* runtime,
820 * you might want to increase epochs(). If you are more interested in *mean* runtime, you might want to increase
821 * minEpochIterations() instead.
822 *
823 * @param numEpochs Number of epochs.
824 */
825 Bench& epochs(size_t numEpochs) noexcept;
826 ANKERL_NANOBENCH(NODISCARD) size_t epochs() const noexcept;
827
828 /**
829 * @brief Upper limit for the runtime of each epoch.
830 *
831 * As a safety precaution if the clock is not very accurate, we can set an upper limit for the maximum evaluation time per
832 * epoch. Default is 100ms. At least a single evaluation of the benchmark is performed.
833 *
834 * @see minEpochTime, minEpochIterations
835 *
836 * @param t Maximum target runtime for a single epoch.
837 */
838 Bench& maxEpochTime(std::chrono::nanoseconds t) noexcept;
839 ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds maxEpochTime() const noexcept;
840
841 /**
842 * @brief Minimum time each epoch should take.
843 *
844 * Default is 1ms, so we are mostly relying on clockResolutionMultiple(). In most cases this is exactly what you want. If you see
845 * that the evaluation is unreliable with a high `err%`, you can increase either minEpochTime() or minEpochIterations().
846 *
847 * @see maxEpochTime, minEpochIterations
848 *
849 * @param t Minimum time each epoch should take.
850 */
851 Bench& minEpochTime(std::chrono::nanoseconds t) noexcept;
852 ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds minEpochTime() const noexcept;
853
854 /**
855 * @brief Sets the minimum number of iterations each epoch should take.
856 *
857 * Default is 1, and we rely on clockResolutionMultiple(). If the `err%` is high and you want a more smooth result, you might want
858 * to increase the minimum number of iterations, or increase the minEpochTime().
859 *
860 * @see minEpochTime, maxEpochTime, minEpochIterations
861 *
862 * @param numIters Minimum number of iterations per epoch.
863 */
864 Bench& minEpochIterations(uint64_t numIters) noexcept;
865 ANKERL_NANOBENCH(NODISCARD) uint64_t minEpochIterations() const noexcept;
866
867 /**
868 * Sets exactly the number of iterations for each epoch. Ignores all other epoch limits. This forces nanobench to use exactly
869 * the given number of iterations for each epoch, not more and not less. Default is 0 (disabled).
870 *
871 * @param numIters Exact number of iterations to use. Set to 0 to disable.
872 */
873 Bench& epochIterations(uint64_t numIters) noexcept;
874 ANKERL_NANOBENCH(NODISCARD) uint64_t epochIterations() const noexcept;
875
876 /**
877 * @brief Sets a number of iterations that are initially performed without any measurements.
878 *
879 * Some benchmarks need a few evaluations to warm up caches / database / whatever access. Normally this should not be needed, since
880 * we show the median result so initial outliers will be filtered away automatically. If the warmup effect is large though, you
881 * might want to set it. Default is 0.
882 *
883 * @param numWarmupIters Number of warmup iterations.
884 */
885 Bench& warmup(uint64_t numWarmupIters) noexcept;
886 ANKERL_NANOBENCH(NODISCARD) uint64_t warmup() const noexcept;
887
888 /**
889 * @brief Marks the next run as the baseline.
890 *
891 * Call `relative(true)` to mark the run as the baseline. Successive runs will be compared to this run. It is calculated by
892 *
893 * @f[
894 * 100\% * \frac{baseline}{runtime}
895 * @f]
896 *
897 * * 100% means it is exactly as fast as the baseline
898 * * >100% means it is faster than the baseline. E.g. 200% means the current run is twice as fast as the baseline.
899 * * <100% means it is slower than the baseline. E.g. 50% means it is twice as slow as the baseline.
900 *
901 * See the tutorial section "Comparing Results" for example usage.
902 *
903 * @param isRelativeEnabled True to enable processing
904 */
905 Bench& relative(bool isRelativeEnabled) noexcept;
906 ANKERL_NANOBENCH(NODISCARD) bool relative() const noexcept;
907
908 /**
909 * @brief Enables/disables performance counters.
910 *
911 * On Linux nanobench has a powerful feature to use performance counters. This enables counting of retired instructions, count
912 * number of branches, missed branches, etc. On default this is enabled, but you can disable it if you don't need that feature.
913 *
914 * @param showPerformanceCounters True to enable, false to disable.
915 */
916 Bench& performanceCounters(bool showPerformanceCounters) noexcept;
917 ANKERL_NANOBENCH(NODISCARD) bool performanceCounters() const noexcept;
918
919 /**
920 * @brief Retrieves all benchmark results collected by the bench object so far.
921 *
922 * Each call to run() generates a Result that is stored within the Bench instance. This is mostly for advanced users who want to
923 * see all the nitty gritty details.
924 *
925 * @return All results collected so far.
926 */
927 ANKERL_NANOBENCH(NODISCARD) std::vector<Result> const& results() const noexcept;
928
929 /*!
930 @verbatim embed:rst
931
932 Convenience shortcut to :cpp:func:`ankerl::nanobench::doNotOptimizeAway`.
933
934 @endverbatim
935 */
936 template <typename Arg>
937 Bench& doNotOptimizeAway(Arg&& arg);
938
939 /*!
940 @verbatim embed:rst
941
942 Sets N for asymptotic complexity calculation, so it becomes possible to calculate `Big O
943 <https://en.wikipedia.org/wiki/Big_O_notation>`_ from multiple benchmark evaluations.
944
945 Use :cpp:func:`ankerl::nanobench::Bench::complexityBigO` when the evaluation has finished. See the tutorial
946 :ref:`asymptotic-complexity` for details.
947
948 @endverbatim
949
950 @tparam T Any type is cast to `double`.
951 @param n Length of N for the next benchmark run, so it is possible to calculate `bigO`.
952 */
953 template <typename T>
954 Bench& complexityN(T n) noexcept;
955 ANKERL_NANOBENCH(NODISCARD) double complexityN() const noexcept;
956
957 /*!
958 Calculates [Big O](https://en.wikipedia.org/wiki/Big_O_notation>) of the results with all preconfigured complexity functions.
959 Currently these complexity functions are fitted into the benchmark results:
960
961 @f$ \mathcal{O}(1) @f$,
962 @f$ \mathcal{O}(n) @f$,
963 @f$ \mathcal{O}(\log{}n) @f$,
964 @f$ \mathcal{O}(n\log{}n) @f$,
965 @f$ \mathcal{O}(n^2) @f$,
966 @f$ \mathcal{O}(n^3) @f$.
967
968 If we e.g. evaluate the complexity of `std::sort`, this is the result of `std::cout << bench.complexityBigO()`:
969
970 ```
971 | coefficient | err% | complexity
972 |--------------:|-------:|------------
973 | 5.08935e-09 | 2.6% | O(n log n)
974 | 6.10608e-08 | 8.0% | O(n)
975 | 1.29307e-11 | 47.2% | O(n^2)
976 | 2.48677e-15 | 69.6% | O(n^3)
977 | 9.88133e-06 | 132.3% | O(log n)
978 | 5.98793e-05 | 162.5% | O(1)
979 ```
980
981 So in this case @f$ \mathcal{O}(n\log{}n) @f$ provides the best approximation.
982
983 @verbatim embed:rst
984 See the tutorial :ref:`asymptotic-complexity` for details.
985 @endverbatim
986 @return Evaluation results, which can be printed or otherwise inspected.
987 */
988 std::vector<BigO> complexityBigO() const;
989
990 /**
991 * @brief Calculates bigO for a custom function.
992 *
993 * E.g. to calculate the mean squared error for @f$ \mathcal{O}(\log{}\log{}n) @f$, which is not part of the default set of
994 * complexityBigO(), you can do this:
995 *
996 * ```
997 * auto logLogN = bench.complexityBigO("O(log log n)", [](double n) {
998 * return std::log2(std::log2(n));
999 * });
1000 * ```
1001 *
1002 * The resulting mean squared error can be printed with `std::cout << logLogN`. E.g. it prints something like this:
1003 *
1004 * ```text
1005 * 2.46985e-05 * O(log log n), rms=1.48121
1006 * ```
1007 *
1008 * @tparam Op Type of mapping operation.
1009 * @param name Name for the function, e.g. "O(log log n)"
1010 * @param op Op's operator() maps a `double` with the desired complexity function, e.g. `log2(log2(n))`.
1011 * @return BigO Error calculation, which is streamable to std::cout.
1012 */
1013 template <typename Op>
1014 BigO complexityBigO(char const* name, Op op) const;
1015
1016 template <typename Op>
1017 BigO complexityBigO(std::string const& name, Op op) const;
1018
1019 /*!
1020 @verbatim embed:rst
1021
1022 Convenience shortcut to :cpp:func:`ankerl::nanobench::render`.
1023
1024 @endverbatim
1025 */
1026 Bench& render(char const* templateContent, std::ostream& os);
1027 Bench& render(std::string const& templateContent, std::ostream& os);
1028
1029 Bench& config(Config const& benchmarkConfig);
1030 ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept;
1031
1032 /**
1033 * @brief Configure an untimed setup step per epoch (forces single-iteration epochs).
1034 *
1035 * Example: `bench.setup(...).run(...);`
1036 */
1037 template <typename SetupOp>
1038 detail::SetupRunner<SetupOp> setup(SetupOp setupOp);
1039
1040 private:
1041 template <typename SetupOp, typename Op>
1042 Bench& runImpl(SetupOp& setupOp, Op&& op);
1043
1044 template <typename SetupOp>
1045 friend class detail::SetupRunner;
1046
1047 Config mConfig{};
1048 std::vector<Result> mResults{};
1049 };
1050 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
1051
1052 /**
1053 * @brief Makes sure none of the given arguments are optimized away by the compiler.
1054 *
1055 * @tparam Arg Type of the argument that shouldn't be optimized away.
1056 * @param arg The input that we mark as being used, even though we don't do anything with it.
1057 */
1058 template <typename Arg>
1059 void doNotOptimizeAway(Arg&& arg);
1060
1061 namespace detail {
1062
1063 #if defined(_MSC_VER)
1064 void doNotOptimizeAwaySink(void const*);
1065
1066 template <typename T>
1067 void doNotOptimizeAway(T const& val);
1068
1069 #else
1070
1071 // These assembly magic is directly from what Google Benchmark is doing. I have previously used what facebook's folly was doing, but
1072 // this seemed to have compilation problems in some cases. Google Benchmark seemed to be the most well tested anyways.
1073 // see https://github.com/google/benchmark/blob/v1.7.1/include/benchmark/benchmark.h#L443-L446
1074 template <typename T>
1075 void doNotOptimizeAway(T const& val) {
1076 // NOLINTNEXTLINE(hicpp-no-assembler)
1077 asm volatile("" : : "r,m"(val) : "memory");
1078 }
1079
1080 template <typename T>
1081 void doNotOptimizeAway(T& val) {
1082 # if defined(__clang__)
1083 // NOLINTNEXTLINE(hicpp-no-assembler)
1084 asm volatile("" : "+r,m"(val) : : "memory");
1085 # else
1086 // NOLINTNEXTLINE(hicpp-no-assembler)
1087 asm volatile("" : "+m,r"(val) : : "memory");
1088 # endif
1089 }
1090 #endif
1091
1092 // internally used, but visible because run() is templated.
1093 // Not movable/copy-able, so we simply use a pointer instead of unique_ptr. This saves us from
1094 // having to include <memory>, and the template instantiation overhead of unique_ptr which is unfortunately quite significant.
1095 ANKERL_NANOBENCH(IGNORE_EFFCPP_PUSH)
1096 class IterationLogic {
1097 public:
1098 explicit IterationLogic(Bench const& bench);
1099 IterationLogic(IterationLogic&&) = delete;
1100 IterationLogic& operator=(IterationLogic&&) = delete;
1101 IterationLogic(IterationLogic const&) = delete;
1102 IterationLogic& operator=(IterationLogic const&) = delete;
1103 ~IterationLogic();
1104
1105 ANKERL_NANOBENCH(NODISCARD) uint64_t numIters() const noexcept;
1106 void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept;
1107 void moveResultTo(std::vector<Result>& results) noexcept;
1108
1109 private:
1110 struct Impl;
1111 Impl* mPimpl;
1112 };
1113 ANKERL_NANOBENCH(IGNORE_EFFCPP_POP)
1114
1115 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
1116 class PerformanceCounters {
1117 public:
1118 PerformanceCounters(PerformanceCounters const&) = delete;
1119 PerformanceCounters(PerformanceCounters&&) = delete;
1120 PerformanceCounters& operator=(PerformanceCounters const&) = delete;
1121 PerformanceCounters& operator=(PerformanceCounters&&) = delete;
1122
1123 PerformanceCounters();
1124 ~PerformanceCounters();
1125
1126 void beginMeasure();
1127 void endMeasure();
1128 void updateResults(uint64_t numIters);
1129
1130 ANKERL_NANOBENCH(NODISCARD) PerfCountSet<uint64_t> const& val() const noexcept;
1131 ANKERL_NANOBENCH(NODISCARD) PerfCountSet<bool> const& has() const noexcept;
1132
1133 private:
1134 #if ANKERL_NANOBENCH(PERF_COUNTERS)
1135 LinuxPerformanceCounters* mPc = nullptr;
1136 #endif
1137 PerfCountSet<uint64_t> mVal{};
1138 PerfCountSet<bool> mHas{};
1139 };
1140 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
1141
1142 // Gets the singleton
1143 PerformanceCounters& performanceCounters();
1144
1145 } // namespace detail
1146
1147 class BigO {
1148 public:
1149 using RangeMeasure = std::vector<std::pair<double, double>>;
1150
1151 template <typename Op>
1152 static RangeMeasure mapRangeMeasure(RangeMeasure data, Op op) {
1153 for (auto& rangeMeasure : data) {
1154 rangeMeasure.first = op(rangeMeasure.first);
1155 }
1156 return data;
1157 }
1158
1159 static RangeMeasure collectRangeMeasure(std::vector<Result> const& results);
1160
1161 template <typename Op>
1162 BigO(char const* bigOName, RangeMeasure const& rangeMeasure, Op rangeToN)
1163 : BigO(bigOName, mapRangeMeasure(rangeMeasure, rangeToN)) {}
1164
1165 template <typename Op>
1166 BigO(std::string bigOName, RangeMeasure const& rangeMeasure, Op rangeToN)
1167 : BigO(std::move(bigOName), mapRangeMeasure(rangeMeasure, rangeToN)) {}
1168
1169 BigO(char const* bigOName, RangeMeasure const& scaledRangeMeasure);
1170 BigO(std::string bigOName, RangeMeasure const& scaledRangeMeasure);
1171 ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept;
1172 ANKERL_NANOBENCH(NODISCARD) double constant() const noexcept;
1173 ANKERL_NANOBENCH(NODISCARD) double normalizedRootMeanSquare() const noexcept;
1174 ANKERL_NANOBENCH(NODISCARD) bool operator<(BigO const& other) const noexcept;
1175
1176 private:
1177 std::string mName{};
1178 double mConstant{};
1179 double mNormalizedRootMeanSquare{};
1180 };
1181 std::ostream& operator<<(std::ostream& os, BigO const& bigO);
1182 std::ostream& operator<<(std::ostream& os, std::vector<ankerl::nanobench::BigO> const& bigOs);
1183
1184 } // namespace nanobench
1185 } // namespace ankerl
1186
1187 // implementation /////////////////////////////////////////////////////////////////////////////////
1188
1189 namespace ankerl {
1190 namespace nanobench {
1191
1192 constexpr uint64_t(Rng::min)() {
1193 return 0;
1194 }
1195
1196 constexpr uint64_t(Rng::max)() {
1197 return (std::numeric_limits<uint64_t>::max)();
1198 }
1199
1200 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
1201 uint64_t Rng::operator()() noexcept {
1202 auto x = mX;
1203
1204 mX = UINT64_C(15241094284759029579) * mY;
1205 mY = rotl(mY - x, 27);
1206
1207 return x;
1208 }
1209
1210 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
1211 uint32_t Rng::bounded(uint32_t range) noexcept {
1212 uint64_t const r32 = static_cast<uint32_t>(operator()());
1213 auto multiresult = r32 * range;
1214 return static_cast<uint32_t>(multiresult >> 32U);
1215 }
1216
1217 double Rng::uniform01() noexcept {
1218 auto i = (UINT64_C(0x3ff) << 52U) | (operator()() >> 12U);
1219 // can't use union in c++ here for type puning, it's undefined behavior.
1220 // std::memcpy is optimized anyways.
1221 double d{};
1222 std::memcpy(&d, &i, sizeof(double));
1223 return d - 1.0;
1224 }
1225
1226 template <typename Container>
1227 void Rng::shuffle(Container& container) noexcept {
1228 auto i = container.size();
1229 while (i > 1U) {
1230 using std::swap;
1231 auto n = operator()();
1232 // using decltype(i) instead of size_t to be compatible to containers with 32bit index (see #80)
1233 auto b1 = static_cast<decltype(i)>((static_cast<uint32_t>(n) * static_cast<uint64_t>(i)) >> 32U);
1234 swap(container[--i], container[b1]);
1235
1236 auto b2 = static_cast<decltype(i)>(((n >> 32U) * static_cast<uint64_t>(i)) >> 32U);
1237 swap(container[--i], container[b2]);
1238 }
1239 }
1240
1241 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
1242 constexpr uint64_t Rng::rotl(uint64_t x, unsigned k) noexcept {
1243 return (x << k) | (x >> (64U - k));
1244 }
1245
1246 namespace detail {
1247
1248 template <typename SetupOp>
1249 class SetupRunner {
1250 public:
1251 explicit SetupRunner(SetupOp setupOp, Bench& bench)
1252 : mSetupOp(std::move(setupOp))
1253 , mBench(bench) {}
1254
1255 template <typename Op>
1256 ANKERL_NANOBENCH_NO_SANITIZE("integer")
1257 Bench& run(Op&& op) {
1258 assert((mBench.epochIterations() <= 1) &&
1259 "setup() runs once per epoch, not once per iteration; it requires epochIterations(1)");
1260 mBench.epochIterations(1);
1261 return mBench.runImpl(mSetupOp, std::forward<Op>(op));
1262 }
1263
1264 private:
1265 SetupOp mSetupOp;
1266 Bench& mBench;
1267 };
1268 } // namespace detail
1269
1270 template <typename Op>
1271 ANKERL_NANOBENCH_NO_SANITIZE("integer")
1272 Bench& Bench::run(Op&& op) {
1273 auto setupOp = [] {};
1274 return runImpl(setupOp, std::forward<Op>(op));
1275 }
1276
1277 template <typename SetupOp, typename Op>
1278 ANKERL_NANOBENCH_NO_SANITIZE("integer")
1279 Bench& Bench::runImpl(SetupOp& setupOp, Op&& op) {
1280 // It is important that this method is kept short so the compiler can do better optimizations/ inlining of op()
1281 detail::IterationLogic iterationLogic(*this);
1282 auto& pc = detail::performanceCounters();
1283
1284 while (auto n = iterationLogic.numIters()) {
1285 setupOp();
1286
1287 pc.beginMeasure();
1288 Clock::time_point const before = Clock::now();
1289 while (n-- > 0) {
1290 op();
1291 }
1292 Clock::time_point const after = Clock::now();
1293 pc.endMeasure();
1294 pc.updateResults(iterationLogic.numIters());
1295 iterationLogic.add(after - before, pc);
1296 }
1297 iterationLogic.moveResultTo(mResults);
1298 return *this;
1299 }
1300
1301 template <typename SetupOp>
1302 detail::SetupRunner<SetupOp> Bench::setup(SetupOp setupOp) {
1303 return detail::SetupRunner<SetupOp>(std::move(setupOp), *this);
1304 }
1305
1306 // Performs all evaluations.
1307 template <typename Op>
1308 Bench& Bench::run(char const* benchmarkName, Op&& op) {
1309 name(benchmarkName);
1310 return run(std::forward<Op>(op));
1311 }
1312
1313 template <typename Op>
1314 Bench& Bench::run(std::string const& benchmarkName, Op&& op) {
1315 name(benchmarkName);
1316 return run(std::forward<Op>(op));
1317 }
1318
1319 template <typename Op>
1320 BigO Bench::complexityBigO(char const* benchmarkName, Op op) const {
1321 return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op);
1322 }
1323
1324 template <typename Op>
1325 BigO Bench::complexityBigO(std::string const& benchmarkName, Op op) const {
1326 return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op);
1327 }
1328
1329 // Set the batch size, e.g. number of processed bytes, or some other metric for the size of the processed data in each iteration.
1330 // Any argument is cast to double.
1331 template <typename T>
1332 Bench& Bench::batch(T b) noexcept {
1333 mConfig.mBatch = static_cast<double>(b);
1334 return *this;
1335 }
1336
1337 // Sets the computation complexity of the next run. Any argument is cast to double.
1338 template <typename T>
1339 Bench& Bench::complexityN(T n) noexcept {
1340 mConfig.mComplexityN = static_cast<double>(n);
1341 return *this;
1342 }
1343
1344 // Convenience: makes sure none of the given arguments are optimized away by the compiler.
1345 template <typename Arg>
1346 Bench& Bench::doNotOptimizeAway(Arg&& arg) {
1347 detail::doNotOptimizeAway(std::forward<Arg>(arg));
1348 return *this;
1349 }
1350
1351 // Makes sure none of the given arguments are optimized away by the compiler.
1352 template <typename Arg>
1353 void doNotOptimizeAway(Arg&& arg) {
1354 detail::doNotOptimizeAway(std::forward<Arg>(arg));
1355 }
1356
1357 namespace detail {
1358
1359 #if defined(_MSC_VER)
1360 template <typename T>
1361 void doNotOptimizeAway(T const& val) {
1362 doNotOptimizeAwaySink(&val);
1363 }
1364
1365 #endif
1366
1367 } // namespace detail
1368 } // namespace nanobench
1369 } // namespace ankerl
1370
1371 #if defined(ANKERL_NANOBENCH_IMPLEMENT)
1372
1373 ///////////////////////////////////////////////////////////////////////////////////////////////////
1374 // implementation part - only visible in .cpp
1375 ///////////////////////////////////////////////////////////////////////////////////////////////////
1376
1377 # include <algorithm> // sort, reverse
1378 # include <cmath>
1379 # include <compare>
1380 # include <cstdlib> // getenv
1381 # include <fstream> // ifstream to parse proc files
1382 # include <functional>
1383 # include <iomanip> // setw, setprecision
1384 # include <iostream> // cout
1385 # include <iterator>
1386 # include <locale>
1387 # include <numeric> // accumulate
1388 # include <random> // random_device
1389 # include <sstream> // to_s in Number
1390 # include <stdexcept> // throw for rendering templates
1391 # include <tuple> // std::tie
1392 # if defined(__linux__)
1393 # include <unistd.h> //sysconf
1394 # endif
1395 # if ANKERL_NANOBENCH(PERF_COUNTERS)
1396 # include <map> // map
1397
1398 # include <linux/perf_event.h>
1399 # include <sys/ioctl.h>
1400 # include <sys/syscall.h>
1401 # include <sys/types.h>
1402 # endif
1403
1404 // declarations ///////////////////////////////////////////////////////////////////////////////////
1405
1406 // As definitions follow these declarations within this
1407 // header, IWYU considers some of them redundant and
1408 // suggests removing them. Disable this IWYU behavior.
1409 // IWYU pragma: begin_keep
1410
1411 namespace ankerl {
1412 namespace nanobench {
1413
1414 // helper stuff that is only intended to be used internally
1415 namespace detail {
1416
1417 // formatting utilities
1418 namespace fmt {
1419
1420 class NumSep;
1421 class StreamStateRestorer;
1422 class Number;
1423 class MarkDownColumn;
1424 class MarkDownCode;
1425
1426 } // namespace fmt
1427 } // namespace detail
1428 } // namespace nanobench
1429 } // namespace ankerl
1430
1431 // IWYU pragma: end_keep
1432
1433 // definitions ////////////////////////////////////////////////////////////////////////////////////
1434
1435 namespace ankerl {
1436 namespace nanobench {
1437
1438 uint64_t splitMix64(uint64_t& state) noexcept;
1439
1440 namespace detail {
1441
1442 // helpers to get double values
1443 template <typename T>
1444 inline double d(T t) noexcept {
1445 return static_cast<double>(t);
1446 }
1447 inline double d(Clock::duration duration) noexcept {
1448 return std::chrono::duration_cast<std::chrono::duration<double>>(duration).count();
1449 }
1450
1451 // Calculates clock resolution once, and remembers the result
1452 inline Clock::duration clockResolution() noexcept;
1453
1454 } // namespace detail
1455
1456 namespace templates {
1457
1458 char const* csv() noexcept {
1459 return R"DELIM("title";"name";"unit";"batch";"elapsed";"error %";"instructions";"branches";"branch misses";"total"
1460 {{#result}}"{{title}}";"{{name}}";"{{unit}}";{{batch}};{{median(elapsed)}};{{medianAbsolutePercentError(elapsed)}};{{median(instructions)}};{{median(branchinstructions)}};{{median(branchmisses)}};{{sumProduct(iterations, elapsed)}}
1461 {{/result}})DELIM";
1462 }
1463
1464 char const* htmlBoxplot() noexcept {
1465 return R"DELIM(<html>
1466
1467 <head>
1468 <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
1469 </head>
1470
1471 <body>
1472 <div id="myDiv"></div>
1473 <script>
1474 var data = [
1475 {{#result}}{
1476 name: '{{name}}',
1477 y: [{{#measurement}}{{elapsed}}{{^-last}}, {{/last}}{{/measurement}}],
1478 },
1479 {{/result}}
1480 ];
1481 var title = '{{title}}';
1482
1483 data = data.map(a => Object.assign(a, { boxpoints: 'all', pointpos: 0, type: 'box' }));
1484 var layout = { title: { text: title }, showlegend: false, yaxis: { title: 'time per unit', rangemode: 'tozero', autorange: true } }; Plotly.newPlot('myDiv', data, layout, {responsive: true});
1485 </script>
1486 </body>
1487
1488 </html>)DELIM";
1489 }
1490
1491 char const* pyperf() noexcept {
1492 return R"DELIM({
1493 "benchmarks": [
1494 {
1495 "runs": [
1496 {
1497 "values": [
1498 {{#measurement}} {{elapsed}}{{^-last}},
1499 {{/last}}{{/measurement}}
1500 ]
1501 }
1502 ]
1503 }
1504 ],
1505 "metadata": {
1506 "loops": {{sum(iterations)}},
1507 "inner_loops": {{batch}},
1508 "name": "{{title}}",
1509 "unit": "second"
1510 },
1511 "version": "1.0"
1512 })DELIM";
1513 }
1514
1515 char const* json() noexcept {
1516 return R"DELIM({
1517 "results": [
1518 {{#result}} {
1519 "title": "{{title}}",
1520 "name": "{{name}}",
1521 "unit": "{{unit}}",
1522 "batch": {{batch}},
1523 "complexityN": {{complexityN}},
1524 "epochs": {{epochs}},
1525 "clockResolution": {{clockResolution}},
1526 "clockResolutionMultiple": {{clockResolutionMultiple}},
1527 "maxEpochTime": {{maxEpochTime}},
1528 "minEpochTime": {{minEpochTime}},
1529 "minEpochIterations": {{minEpochIterations}},
1530 "epochIterations": {{epochIterations}},
1531 "warmup": {{warmup}},
1532 "relative": {{relative}},
1533 "median(elapsed)": {{median(elapsed)}},
1534 "medianAbsolutePercentError(elapsed)": {{medianAbsolutePercentError(elapsed)}},
1535 "median(instructions)": {{median(instructions)}},
1536 "medianAbsolutePercentError(instructions)": {{medianAbsolutePercentError(instructions)}},
1537 "median(cpucycles)": {{median(cpucycles)}},
1538 "median(contextswitches)": {{median(contextswitches)}},
1539 "median(pagefaults)": {{median(pagefaults)}},
1540 "median(branchinstructions)": {{median(branchinstructions)}},
1541 "median(branchmisses)": {{median(branchmisses)}},
1542 "totalTime": {{sumProduct(iterations, elapsed)}},
1543 "measurements": [
1544 {{#measurement}} {
1545 "iterations": {{iterations}},
1546 "elapsed": {{elapsed}},
1547 "pagefaults": {{pagefaults}},
1548 "cpucycles": {{cpucycles}},
1549 "contextswitches": {{contextswitches}},
1550 "instructions": {{instructions}},
1551 "branchinstructions": {{branchinstructions}},
1552 "branchmisses": {{branchmisses}}
1553 }{{^-last}},{{/-last}}
1554 {{/measurement}} ]
1555 }{{^-last}},{{/-last}}
1556 {{/result}} ]
1557 })DELIM";
1558 }
1559
1560 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
1561 struct Node {
1562 enum class Type { tag, content, section, inverted_section };
1563
1564 char const* begin;
1565 char const* end;
1566 std::vector<Node> children;
1567 Type type;
1568
1569 template <size_t N>
1570 // NOLINTNEXTLINE(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
1571 bool operator==(char const (&str)[N]) const noexcept {
1572 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
1573 return static_cast<size_t>(std::distance(begin, end) + 1) == N && 0 == strncmp(str, begin, N - 1);
1574 }
1575 };
1576 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
1577
1578 // NOLINTNEXTLINE(misc-no-recursion)
1579 static std::vector<Node> parseMustacheTemplate(char const** tpl) {
1580 std::vector<Node> nodes;
1581
1582 while (true) {
1583 auto const* begin = std::strstr(*tpl, "{{");
1584 auto const* end = begin;
1585 if (begin != nullptr) {
1586 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1587 begin += 2;
1588 end = std::strstr(begin, "}}");
1589 }
1590
1591 if (begin == nullptr || end == nullptr) {
1592 // nothing found, finish node
1593 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1594 nodes.emplace_back(Node{*tpl, *tpl + std::strlen(*tpl), std::vector<Node>{}, Node::Type::content});
1595 return nodes;
1596 }
1597
1598 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1599 nodes.emplace_back(Node{*tpl, begin - 2, std::vector<Node>{}, Node::Type::content});
1600
1601 // we found a tag
1602 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1603 *tpl = end + 2;
1604 switch (*begin) {
1605 case '/':
1606 // finished! bail out
1607 return nodes;
1608
1609 case '#':
1610 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1611 nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::section});
1612 break;
1613
1614 case '^':
1615 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1616 nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::inverted_section});
1617 break;
1618
1619 default:
1620 nodes.emplace_back(Node{begin, end, std::vector<Node>{}, Node::Type::tag});
1621 break;
1622 }
1623 }
1624 }
1625
1626 static bool generateFirstLast(Node const& n, size_t idx, size_t size, std::ostream& out) {
1627 ANKERL_NANOBENCH_LOG("n.type=" << static_cast<int>(n.type));
1628 bool const matchFirst = n == "-first";
1629 bool const matchLast = n == "-last";
1630 if (!matchFirst && !matchLast) {
1631 return false;
1632 }
1633
1634 bool doWrite = false;
1635 if (n.type == Node::Type::section) {
1636 doWrite = (matchFirst && idx == 0) || (matchLast && idx == size - 1);
1637 } else if (n.type == Node::Type::inverted_section) {
1638 doWrite = (matchFirst && idx != 0) || (matchLast && idx != size - 1);
1639 }
1640
1641 if (doWrite) {
1642 for (auto const& child : n.children) {
1643 if (child.type == Node::Type::content) {
1644 out.write(child.begin, std::distance(child.begin, child.end));
1645 }
1646 }
1647 }
1648 return true;
1649 }
1650
1651 static bool matchCmdArgs(std::string const& str, std::vector<std::string>& matchResult) {
1652 matchResult.clear();
1653 auto idxOpen = str.find('(');
1654 auto idxClose = str.find(')', idxOpen);
1655 if (idxClose == std::string::npos) {
1656 return false;
1657 }
1658
1659 matchResult.emplace_back(str.substr(0, idxOpen));
1660
1661 // split by comma
1662 matchResult.emplace_back();
1663 for (size_t i = idxOpen + 1; i != idxClose; ++i) {
1664 if (str[i] == ' ' || str[i] == '\t') {
1665 // skip whitespace
1666 continue;
1667 }
1668 if (str[i] == ',') {
1669 // got a comma => new string
1670 matchResult.emplace_back();
1671 continue;
1672 }
1673 // no whitespace no comma, append
1674 matchResult.back() += str[i];
1675 }
1676 return true;
1677 }
1678
1679 static bool generateConfigTag(Node const& n, Config const& config, std::ostream& out) {
1680 using detail::d;
1681
1682 if (n == "title") {
1683 out << config.mBenchmarkTitle;
1684 return true;
1685 }
1686 if (n == "name") {
1687 out << config.mBenchmarkName;
1688 return true;
1689 }
1690 if (n == "unit") {
1691 out << config.mUnit;
1692 return true;
1693 }
1694 if (n == "batch") {
1695 out << config.mBatch;
1696 return true;
1697 }
1698 if (n == "complexityN") {
1699 out << config.mComplexityN;
1700 return true;
1701 }
1702 if (n == "epochs") {
1703 out << config.mNumEpochs;
1704 return true;
1705 }
1706 if (n == "clockResolution") {
1707 out << d(detail::clockResolution());
1708 return true;
1709 }
1710 if (n == "clockResolutionMultiple") {
1711 out << config.mClockResolutionMultiple;
1712 return true;
1713 }
1714 if (n == "maxEpochTime") {
1715 out << d(config.mMaxEpochTime);
1716 return true;
1717 }
1718 if (n == "minEpochTime") {
1719 out << d(config.mMinEpochTime);
1720 return true;
1721 }
1722 if (n == "minEpochIterations") {
1723 out << config.mMinEpochIterations;
1724 return true;
1725 }
1726 if (n == "epochIterations") {
1727 out << config.mEpochIterations;
1728 return true;
1729 }
1730 if (n == "warmup") {
1731 out << config.mWarmup;
1732 return true;
1733 }
1734 if (n == "relative") {
1735 out << config.mIsRelative;
1736 return true;
1737 }
1738 return false;
1739 }
1740
1741 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
1742 static std::ostream& generateResultTag(Node const& n, Result const& r, std::ostream& out) {
1743 if (generateConfigTag(n, r.config(), out)) {
1744 return out;
1745 }
1746 // match e.g. "median(elapsed)"
1747 // g++ 4.8 doesn't implement std::regex :(
1748 // static std::regex const regOpArg1("^([a-zA-Z]+)\\(([a-zA-Z]*)\\)$");
1749 // std::cmatch matchResult;
1750 // if (std::regex_match(n.begin, n.end, matchResult, regOpArg1)) {
1751 std::vector<std::string> matchResult;
1752 if (matchCmdArgs(std::string(n.begin, n.end), matchResult)) {
1753 if (matchResult.size() == 2) {
1754 if (matchResult[0] == "context") {
1755 return out << r.context(matchResult[1]);
1756 }
1757
1758 auto m = Result::fromString(matchResult[1]);
1759 if (m == Result::Measure::_size) {
1760 return out << 0.0;
1761 }
1762
1763 if (matchResult[0] == "median") {
1764 return out << r.median(m);
1765 }
1766 if (matchResult[0] == "average") {
1767 return out << r.average(m);
1768 }
1769 if (matchResult[0] == "medianAbsolutePercentError") {
1770 return out << r.medianAbsolutePercentError(m);
1771 }
1772 if (matchResult[0] == "sum") {
1773 return out << r.sum(m);
1774 }
1775 if (matchResult[0] == "minimum") {
1776 return out << r.minimum(m);
1777 }
1778 if (matchResult[0] == "maximum") {
1779 return out << r.maximum(m);
1780 }
1781 } else if (matchResult.size() == 3) {
1782 auto m1 = Result::fromString(matchResult[1]);
1783 auto m2 = Result::fromString(matchResult[2]);
1784 if (m1 == Result::Measure::_size || m2 == Result::Measure::_size) {
1785 return out << 0.0;
1786 }
1787
1788 if (matchResult[0] == "sumProduct") {
1789 return out << r.sumProduct(m1, m2);
1790 }
1791 }
1792 }
1793
1794 // match e.g. "sumProduct(elapsed, iterations)"
1795 // static std::regex const regOpArg2("^([a-zA-Z]+)\\(([a-zA-Z]*)\\s*,\\s+([a-zA-Z]*)\\)$");
1796
1797 // nothing matches :(
1798 throw std::runtime_error("command '" + std::string(n.begin, n.end) + "' not understood");
1799 }
1800
1801 static void generateResultMeasurement(std::vector<Node> const& nodes, size_t idx, Result const& r, std::ostream& out) {
1802 for (auto const& n : nodes) {
1803 if (!generateFirstLast(n, idx, r.size(), out)) {
1804 ANKERL_NANOBENCH_LOG("n.type=" << static_cast<int>(n.type));
1805 switch (n.type) {
1806 case Node::Type::content:
1807 out.write(n.begin, std::distance(n.begin, n.end));
1808 break;
1809
1810 case Node::Type::inverted_section:
1811 throw std::runtime_error("got a inverted section inside measurement");
1812
1813 case Node::Type::section:
1814 throw std::runtime_error("got a section inside measurement");
1815
1816 case Node::Type::tag: {
1817 auto m = Result::fromString(std::string(n.begin, n.end));
1818 if (m == Result::Measure::_size || !r.has(m)) {
1819 out << 0.0;
1820 } else {
1821 out << r.get(idx, m);
1822 }
1823 break;
1824 }
1825 }
1826 }
1827 }
1828 }
1829
1830 static void generateResult(std::vector<Node> const& nodes, size_t idx, std::vector<Result> const& results, std::ostream& out) {
1831 auto const& r = results[idx];
1832 for (auto const& n : nodes) {
1833 if (!generateFirstLast(n, idx, results.size(), out)) {
1834 ANKERL_NANOBENCH_LOG("n.type=" << static_cast<int>(n.type));
1835 switch (n.type) {
1836 case Node::Type::content:
1837 out.write(n.begin, std::distance(n.begin, n.end));
1838 break;
1839
1840 case Node::Type::inverted_section:
1841 throw std::runtime_error("got a inverted section inside result");
1842
1843 case Node::Type::section:
1844 if (n == "measurement") {
1845 for (size_t i = 0; i < r.size(); ++i) {
1846 generateResultMeasurement(n.children, i, r, out);
1847 }
1848 } else {
1849 throw std::runtime_error("got a section inside result");
1850 }
1851 break;
1852
1853 case Node::Type::tag:
1854 generateResultTag(n, r, out);
1855 break;
1856 }
1857 }
1858 }
1859 }
1860
1861 } // namespace templates
1862
1863 // helper stuff that only intended to be used internally
1864 namespace detail {
1865
1866 char const* getEnv(char const* name);
1867 bool isEndlessRunning(std::string const& name);
1868 bool isWarningsEnabled();
1869
1870 template <typename T>
1871 T parseFile(std::string const& filename, bool* fail);
1872
1873 void gatherStabilityInformation(std::vector<std::string>& warnings, std::vector<std::string>& recommendations);
1874 void printStabilityInformationOnce(std::ostream* outStream);
1875
1876 // remembers the last table settings used. When it changes, a new table header is automatically written for the new entry.
1877 uint64_t& singletonHeaderHash() noexcept;
1878
1879 // determines resolution of the given clock. This is done by measuring multiple times and returning the minimum time difference.
1880 Clock::duration calcClockResolution(size_t numEvaluations) noexcept;
1881
1882 // formatting utilities
1883 namespace fmt {
1884
1885 // adds thousands separator to numbers
1886 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
1887 class NumSep : public std::numpunct<char> {
1888 public:
1889 explicit NumSep(char sep);
1890 char do_thousands_sep() const override;
1891 std::string do_grouping() const override;
1892
1893 private:
1894 char mSep;
1895 };
1896 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
1897
1898 // RAII to save & restore a stream's state
1899 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
1900 class StreamStateRestorer {
1901 public:
1902 explicit StreamStateRestorer(std::ostream& s);
1903 ~StreamStateRestorer();
1904
1905 // sets back all stream info that we remembered at construction
1906 void restore();
1907
1908 // don't allow copying / moving
1909 StreamStateRestorer(StreamStateRestorer const&) = delete;
1910 StreamStateRestorer& operator=(StreamStateRestorer const&) = delete;
1911 StreamStateRestorer(StreamStateRestorer&&) = delete;
1912 StreamStateRestorer& operator=(StreamStateRestorer&&) = delete;
1913
1914 private:
1915 std::ostream& mStream;
1916 std::locale mLocale;
1917 std::streamsize const mPrecision;
1918 std::streamsize const mWidth;
1919 std::ostream::char_type const mFill;
1920 std::ostream::fmtflags const mFmtFlags;
1921 };
1922 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
1923
1924 // Number formatter
1925 class Number {
1926 public:
1927 Number(int width, int precision, double value);
1928 Number(int width, int precision, int64_t value);
1929 ANKERL_NANOBENCH(NODISCARD) std::string to_s() const;
1930
1931 private:
1932 friend std::ostream& operator<<(std::ostream& os, Number const& n);
1933 std::ostream& write(std::ostream& os) const;
1934
1935 int mWidth;
1936 int mPrecision;
1937 double mValue;
1938 };
1939
1940 // helper replacement for std::to_string of signed/unsigned numbers so we are locale independent
1941 std::string to_s(uint64_t n);
1942
1943 std::ostream& operator<<(std::ostream& os, Number const& n);
1944
1945 class MarkDownColumn {
1946 public:
1947 MarkDownColumn(int w, int prec, std::string tit, std::string suff, double val) noexcept;
1948 ANKERL_NANOBENCH(NODISCARD) std::string title() const;
1949 ANKERL_NANOBENCH(NODISCARD) std::string separator() const;
1950 ANKERL_NANOBENCH(NODISCARD) std::string invalid() const;
1951 ANKERL_NANOBENCH(NODISCARD) std::string value() const;
1952
1953 private:
1954 int mWidth;
1955 int mPrecision;
1956 std::string mTitle;
1957 std::string mSuffix;
1958 double mValue;
1959 };
1960
1961 // Formats any text as markdown code, escaping backticks.
1962 class MarkDownCode {
1963 public:
1964 explicit MarkDownCode(std::string const& what);
1965
1966 private:
1967 friend std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode);
1968 std::ostream& write(std::ostream& os) const;
1969
1970 std::string mWhat{};
1971 };
1972
1973 std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode);
1974
1975 } // namespace fmt
1976 } // namespace detail
1977 } // namespace nanobench
1978 } // namespace ankerl
1979
1980 // implementation /////////////////////////////////////////////////////////////////////////////////
1981
1982 namespace ankerl {
1983 namespace nanobench {
1984
1985 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
1986 void render(char const* mustacheTemplate, std::vector<Result> const& results, std::ostream& out) {
1987 detail::fmt::StreamStateRestorer const restorer(out);
1988
1989 out.precision(std::numeric_limits<double>::digits10);
1990 auto nodes = templates::parseMustacheTemplate(&mustacheTemplate);
1991
1992 for (auto const& n : nodes) {
1993 ANKERL_NANOBENCH_LOG("n.type=" << static_cast<int>(n.type));
1994 switch (n.type) {
1995 case templates::Node::Type::content:
1996 out.write(n.begin, std::distance(n.begin, n.end));
1997 break;
1998
1999 case templates::Node::Type::inverted_section:
2000 throw std::runtime_error("unknown list '" + std::string(n.begin, n.end) + "'");
2001
2002 case templates::Node::Type::section:
2003 if (n == "result") {
2004 const size_t nbResults = results.size();
2005 for (size_t i = 0; i < nbResults; ++i) {
2006 generateResult(n.children, i, results, out);
2007 }
2008 } else if (n == "measurement") {
2009 if (results.size() != 1) {
2010 throw std::runtime_error(
2011 "render: can only use section 'measurement' here if there is a single result, but there are " +
2012 detail::fmt::to_s(results.size()));
2013 }
2014 // when we only have a single result, we can immediately go into its measurement.
2015 auto const& r = results.front();
2016 for (size_t i = 0; i < r.size(); ++i) {
2017 generateResultMeasurement(n.children, i, r, out);
2018 }
2019 } else {
2020 throw std::runtime_error("render: unknown section '" + std::string(n.begin, n.end) + "'");
2021 }
2022 break;
2023
2024 case templates::Node::Type::tag:
2025 if (results.size() == 1) {
2026 // result & config are both supported there
2027 generateResultTag(n, results.front(), out);
2028 } else {
2029 // This just uses the last result's config.
2030 if (!generateConfigTag(n, results.back().config(), out)) {
2031 throw std::runtime_error("unknown tag '" + std::string(n.begin, n.end) + "'");
2032 }
2033 }
2034 break;
2035 }
2036 }
2037 }
2038
2039 void render(std::string const& mustacheTemplate, std::vector<Result> const& results, std::ostream& out) {
2040 render(mustacheTemplate.c_str(), results, out);
2041 }
2042
2043 void render(char const* mustacheTemplate, const Bench& bench, std::ostream& out) {
2044 render(mustacheTemplate, bench.results(), out);
2045 }
2046
2047 void render(std::string const& mustacheTemplate, const Bench& bench, std::ostream& out) {
2048 render(mustacheTemplate.c_str(), bench.results(), out);
2049 }
2050
2051 namespace detail {
2052
2053 PerformanceCounters& performanceCounters() {
2054 # if defined(__clang__)
2055 # pragma clang diagnostic push
2056 # pragma clang diagnostic ignored "-Wexit-time-destructors"
2057 # endif
2058 static PerformanceCounters pc;
2059 # if defined(__clang__)
2060 # pragma clang diagnostic pop
2061 # endif
2062 return pc;
2063 }
2064
2065 // Windows version of doNotOptimizeAway
2066 // see https://github.com/google/benchmark/blob/v1.7.1/include/benchmark/benchmark.h#L514
2067 // see https://github.com/facebook/folly/blob/v2023.01.30.00/folly/lang/Hint-inl.h#L54-L58
2068 // see https://learn.microsoft.com/en-us/cpp/preprocessor/optimize
2069 # if defined(_MSC_VER)
2070 # pragma optimize("", off)
2071 void doNotOptimizeAwaySink(void const*) {}
2072 # pragma optimize("", on)
2073 # endif
2074
2075 template <typename T>
2076 T parseFile(std::string const& filename, bool* fail) {
2077 std::ifstream fin(filename); // NOLINT(misc-const-correctness)
2078 T num{};
2079 fin >> num;
2080 if (fail != nullptr) {
2081 *fail = fin.fail();
2082 }
2083 return num;
2084 }
2085
2086 char const* getEnv(char const* name) {
2087 # if defined(_MSC_VER)
2088 # pragma warning(push)
2089 # pragma warning(disable : 4996) // getenv': This function or variable may be unsafe.
2090 # endif
2091 return std::getenv(name); // NOLINT(concurrency-mt-unsafe)
2092 # if defined(_MSC_VER)
2093 # pragma warning(pop)
2094 # endif
2095 }
2096
2097 bool isEndlessRunning(std::string const& name) {
2098 auto const* const endless = getEnv("NANOBENCH_ENDLESS");
2099 return nullptr != endless && endless == name;
2100 }
2101
2102 // True when environment variable NANOBENCH_SUPPRESS_WARNINGS is either not set at all, or set to "0"
2103 bool isWarningsEnabled() {
2104 auto const* const suppression = getEnv("NANOBENCH_SUPPRESS_WARNINGS");
2105 return nullptr == suppression || suppression == std::string("0");
2106 }
2107
2108 void gatherStabilityInformation(std::vector<std::string>& warnings, std::vector<std::string>& recommendations) {
2109 warnings.clear();
2110 recommendations.clear();
2111
2112 # if defined(DEBUG)
2113 warnings.emplace_back("DEBUG defined");
2114 bool const recommendCheckFlags = true;
2115 # else
2116 bool const recommendCheckFlags = false;
2117 # endif
2118
2119 bool recommendPyPerf = false;
2120 # if defined(__linux__)
2121 auto nprocs = sysconf(_SC_NPROCESSORS_CONF);
2122 if (nprocs <= 0) {
2123 warnings.emplace_back("couldn't figure out number of processors - no governor, turbo check possible");
2124 } else {
2125 // check frequency scaling
2126 for (long id = 0; id < nprocs; ++id) {
2127 auto idStr = detail::fmt::to_s(static_cast<uint64_t>(id));
2128 auto sysCpu = "/sys/devices/system/cpu/cpu" + idStr;
2129 auto minFreq = parseFile<int64_t>(sysCpu + "/cpufreq/scaling_min_freq", nullptr);
2130 auto maxFreq = parseFile<int64_t>(sysCpu + "/cpufreq/scaling_max_freq", nullptr);
2131 if (minFreq != maxFreq) {
2132 auto minMHz = d(minFreq) / 1000.0;
2133 auto maxMHz = d(maxFreq) / 1000.0;
2134 warnings.emplace_back("CPU frequency scaling enabled: CPU " + idStr + " between " +
2135 detail::fmt::Number(1, 1, minMHz).to_s() + " and " + detail::fmt::Number(1, 1, maxMHz).to_s() +
2136 " MHz");
2137 recommendPyPerf = true;
2138 break;
2139 }
2140 }
2141
2142 auto fail = false;
2143 auto currentGovernor = parseFile<std::string>("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", &fail);
2144 if (!fail && "performance" != currentGovernor) {
2145 warnings.emplace_back("CPU governor is '" + currentGovernor + "' but should be 'performance'");
2146 recommendPyPerf = true;
2147 }
2148
2149 auto noTurbo = parseFile<int>("/sys/devices/system/cpu/intel_pstate/no_turbo", &fail);
2150 if (!fail && noTurbo == 0) {
2151 warnings.emplace_back("Turbo is enabled, CPU frequency will fluctuate");
2152 recommendPyPerf = true;
2153 }
2154 }
2155 # endif
2156
2157 if (recommendCheckFlags) {
2158 recommendations.emplace_back("Make sure you compile for Release");
2159 }
2160 if (recommendPyPerf) {
2161 recommendations.emplace_back("Use 'pyperf system tune' before benchmarking. See https://github.com/psf/pyperf");
2162 }
2163 }
2164
2165 void printStabilityInformationOnce(std::ostream* outStream) {
2166 static bool shouldPrint = true;
2167 if (shouldPrint && (nullptr != outStream) && isWarningsEnabled()) {
2168 auto& os = *outStream;
2169 shouldPrint = false;
2170 std::vector<std::string> warnings;
2171 std::vector<std::string> recommendations;
2172 gatherStabilityInformation(warnings, recommendations);
2173 if (warnings.empty()) {
2174 return;
2175 }
2176
2177 os << "Warning, results might be unstable:" << std::endl;
2178 for (auto const& w : warnings) {
2179 os << "* " << w << std::endl;
2180 }
2181
2182 os << std::endl << "Recommendations" << std::endl;
2183 for (auto const& r : recommendations) {
2184 os << "* " << r << std::endl;
2185 }
2186 }
2187 }
2188
2189 // remembers the last table settings used. When it changes, a new table header is automatically written for the new entry.
2190 uint64_t& singletonHeaderHash() noexcept {
2191 static uint64_t sHeaderHash{};
2192 return sHeaderHash;
2193 }
2194
2195 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
2196 inline uint64_t hash_combine(uint64_t seed, uint64_t val) {
2197 return seed ^ (val + UINT64_C(0x9e3779b9) + (seed << 6U) + (seed >> 2U));
2198 }
2199
2200 // determines resolution of the given clock. This is done by measuring multiple times and returning the minimum time difference.
2201 Clock::duration calcClockResolution(size_t numEvaluations) noexcept {
2202 auto bestDuration = Clock::duration::max();
2203 Clock::time_point tBegin;
2204 Clock::time_point tEnd;
2205 for (size_t i = 0; i < numEvaluations; ++i) {
2206 tBegin = Clock::now();
2207 do {
2208 tEnd = Clock::now();
2209 } while (tBegin == tEnd);
2210 bestDuration = (std::min)(bestDuration, tEnd - tBegin);
2211 }
2212 return bestDuration;
2213 }
2214
2215 // Calculates clock resolution once, and remembers the result
2216 Clock::duration clockResolution() noexcept {
2217 static Clock::duration const sResolution = calcClockResolution(20);
2218 return sResolution;
2219 }
2220
2221 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
2222 struct IterationLogic::Impl {
2223 enum class State { warmup, upscaling_runtime, measuring, endless };
2224
2225 explicit Impl(Bench const& bench)
2226 : mBench(bench)
2227 , mResult(bench.config()) {
2228 printStabilityInformationOnce(mBench.output());
2229
2230 // determine target runtime per epoch
2231 mTargetRuntimePerEpoch = detail::clockResolution() * mBench.clockResolutionMultiple();
2232 if (mTargetRuntimePerEpoch > mBench.maxEpochTime()) {
2233 mTargetRuntimePerEpoch = mBench.maxEpochTime();
2234 }
2235 if (mTargetRuntimePerEpoch < mBench.minEpochTime()) {
2236 mTargetRuntimePerEpoch = mBench.minEpochTime();
2237 }
2238
2239 if (isEndlessRunning(mBench.name())) {
2240 std::cerr << "NANOBENCH_ENDLESS set: running '" << mBench.name() << "' endlessly" << std::endl;
2241 mNumIters = (std::numeric_limits<uint64_t>::max)();
2242 mState = State::endless;
2243 } else if (0 != mBench.warmup()) {
2244 mNumIters = mBench.warmup();
2245 mState = State::warmup;
2246 } else if (0 != mBench.epochIterations()) {
2247 // exact number of iterations
2248 mNumIters = mBench.epochIterations();
2249 mState = State::measuring;
2250 } else {
2251 mNumIters = mBench.minEpochIterations();
2252 mState = State::upscaling_runtime;
2253 }
2254 }
2255
2256 // directly calculates new iters based on elapsed&iters, and adds a 10% noise. Makes sure we don't underflow.
2257 ANKERL_NANOBENCH(NODISCARD) uint64_t calcBestNumIters(std::chrono::nanoseconds elapsed, uint64_t iters) noexcept {
2258 auto doubleElapsed = d(elapsed);
2259 auto doubleTargetRuntimePerEpoch = d(mTargetRuntimePerEpoch);
2260 auto doubleNewIters = doubleTargetRuntimePerEpoch / doubleElapsed * d(iters);
2261
2262 auto doubleMinEpochIters = d(mBench.minEpochIterations());
2263 if (doubleNewIters < doubleMinEpochIters) {
2264 doubleNewIters = doubleMinEpochIters;
2265 }
2266 doubleNewIters *= 1.0 + 0.2 * mRng.uniform01();
2267
2268 // +0.5 for correct rounding when casting
2269 // NOLINTNEXTLINE(bugprone-incorrect-roundings)
2270 return static_cast<uint64_t>(doubleNewIters + 0.5);
2271 }
2272
2273 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") void upscale(std::chrono::nanoseconds elapsed) {
2274 if (elapsed * 10 < mTargetRuntimePerEpoch) {
2275 // we are far below the target runtime. Multiply iterations by 10 (with overflow check)
2276 if (mNumIters * 10 < mNumIters) {
2277 // overflow :-(
2278 showResult("iterations overflow. Maybe your code got optimized away?");
2279 mNumIters = 0;
2280 return;
2281 }
2282 mNumIters *= 10;
2283 } else {
2284 mNumIters = calcBestNumIters(elapsed, mNumIters);
2285 }
2286 }
2287
2288 void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept {
2289 # if defined(ANKERL_NANOBENCH_LOG_ENABLED)
2290 auto oldIters = mNumIters;
2291 # endif
2292
2293 switch (mState) {
2294 case State::warmup:
2295 if (isCloseEnoughForMeasurements(elapsed)) {
2296 // if elapsed is close enough, we can skip upscaling and go right to measurements
2297 // still, we don't add the result to the measurements.
2298 mState = State::measuring;
2299 mNumIters = calcBestNumIters(elapsed, mNumIters);
2300 } else {
2301 // not close enough: switch to upscaling
2302 mState = State::upscaling_runtime;
2303 upscale(elapsed);
2304 }
2305 break;
2306
2307 case State::upscaling_runtime:
2308 if (isCloseEnoughForMeasurements(elapsed)) {
2309 // if we are close enough, add measurement and switch to always measuring
2310 mState = State::measuring;
2311 mTotalElapsed += elapsed;
2312 mTotalNumIters += mNumIters;
2313 mResult.add(elapsed, mNumIters, pc);
2314 mNumIters = calcBestNumIters(mTotalElapsed, mTotalNumIters);
2315 } else {
2316 upscale(elapsed);
2317 }
2318 break;
2319
2320 case State::measuring:
2321 // just add measurements - no questions asked. Even when runtime is low. But we can't ignore
2322 // that fluctuation, or else we would bias the result
2323 mTotalElapsed += elapsed;
2324 mTotalNumIters += mNumIters;
2325 mResult.add(elapsed, mNumIters, pc);
2326 if (0 != mBench.epochIterations()) {
2327 mNumIters = mBench.epochIterations();
2328 } else {
2329 mNumIters = calcBestNumIters(mTotalElapsed, mTotalNumIters);
2330 }
2331 break;
2332
2333 case State::endless:
2334 mNumIters = (std::numeric_limits<uint64_t>::max)();
2335 break;
2336 }
2337
2338 if (static_cast<uint64_t>(mResult.size()) == mBench.epochs()) {
2339 // we got all the results that we need, finish it
2340 showResult("");
2341 mNumIters = 0;
2342 }
2343
2344 ANKERL_NANOBENCH_LOG(mBench.name() << ": " << detail::fmt::Number(20, 3, d(elapsed.count())) << " elapsed, "
2345 << detail::fmt::Number(20, 3, d(mTargetRuntimePerEpoch.count())) << " target. oldIters="
2346 << oldIters << ", mNumIters=" << mNumIters << ", mState=" << static_cast<int>(mState));
2347 }
2348
2349 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
2350 void showResult(std::string const& errorMessage) const {
2351 ANKERL_NANOBENCH_LOG(errorMessage);
2352
2353 if (mBench.output() != nullptr) {
2354 // prepare column data ///////
2355 std::vector<fmt::MarkDownColumn> columns;
2356
2357 auto rMedian = mResult.median(Result::Measure::elapsed);
2358
2359 if (mBench.relative()) {
2360 double d = 100.0;
2361 if (!mBench.results().empty()) {
2362 d = rMedian <= 0.0 ? 0.0 : mBench.results().front().median(Result::Measure::elapsed) / rMedian * 100.0;
2363 }
2364 columns.emplace_back(11, 1, "relative", "%", d);
2365 }
2366
2367 if (mBench.complexityN() > 0) {
2368 columns.emplace_back(14, 0, "complexityN", "", mBench.complexityN());
2369 }
2370
2371 columns.emplace_back(22, 2, mBench.timeUnitName() + "/" + mBench.unit(), "",
2372 rMedian / (mBench.timeUnit().count() * mBench.batch()));
2373 columns.emplace_back(22, 2, mBench.unit() + "/s", "", rMedian <= 0.0 ? 0.0 : mBench.batch() / rMedian);
2374
2375 double const rErrorMedian = mResult.medianAbsolutePercentError(Result::Measure::elapsed);
2376 columns.emplace_back(10, 1, "err%", "%", rErrorMedian * 100.0);
2377
2378 double rInsMedian = -1.0;
2379 if (mBench.performanceCounters() && mResult.has(Result::Measure::instructions)) {
2380 rInsMedian = mResult.median(Result::Measure::instructions);
2381 columns.emplace_back(18, 2, "ins/" + mBench.unit(), "", rInsMedian / mBench.batch());
2382 }
2383
2384 double rCycMedian = -1.0;
2385 if (mBench.performanceCounters() && mResult.has(Result::Measure::cpucycles)) {
2386 rCycMedian = mResult.median(Result::Measure::cpucycles);
2387 columns.emplace_back(18, 2, "cyc/" + mBench.unit(), "", rCycMedian / mBench.batch());
2388 }
2389 if (rInsMedian > 0.0 && rCycMedian > 0.0) {
2390 columns.emplace_back(9, 3, "IPC", "", rCycMedian <= 0.0 ? 0.0 : rInsMedian / rCycMedian);
2391 }
2392 if (mBench.performanceCounters() && mResult.has(Result::Measure::branchinstructions)) {
2393 double const rBraMedian = mResult.median(Result::Measure::branchinstructions);
2394 columns.emplace_back(17, 2, "bra/" + mBench.unit(), "", rBraMedian / mBench.batch());
2395 if (mResult.has(Result::Measure::branchmisses)) {
2396 double p = 0.0;
2397 if (rBraMedian >= 1e-9) {
2398 p = 100.0 * mResult.median(Result::Measure::branchmisses) / rBraMedian;
2399 }
2400 columns.emplace_back(10, 1, "miss%", "%", p);
2401 }
2402 }
2403
2404 columns.emplace_back(12, 2, "total", "", mResult.sumProduct(Result::Measure::iterations, Result::Measure::elapsed));
2405
2406 // write everything
2407 auto& os = *mBench.output();
2408
2409 // combine all elements that are relevant for printing the header
2410 uint64_t hash = 0;
2411 hash = hash_combine(std::hash<std::string>{}(mBench.unit()), hash);
2412 hash = hash_combine(std::hash<std::string>{}(mBench.title()), hash);
2413 hash = hash_combine(std::hash<std::string>{}(mBench.timeUnitName()), hash);
2414 hash = hash_combine(std::hash<double>{}(mBench.timeUnit().count()), hash);
2415 hash = hash_combine(std::hash<bool>{}(mBench.relative()), hash);
2416 hash = hash_combine(std::hash<bool>{}(mBench.performanceCounters()), hash);
2417
2418 if (hash != singletonHeaderHash()) {
2419 singletonHeaderHash() = hash;
2420
2421 // no result yet, print header
2422 os << std::endl;
2423 for (auto const& col : columns) {
2424 os << col.title();
2425 }
2426 os << "| " << mBench.title() << std::endl;
2427
2428 for (auto const& col : columns) {
2429 os << col.separator();
2430 }
2431 os << "|:" << std::string(mBench.title().size() + 1U, '-') << std::endl;
2432 }
2433
2434 if (!errorMessage.empty()) {
2435 for (auto const& col : columns) {
2436 os << col.invalid();
2437 }
2438 os << "| :boom: " << fmt::MarkDownCode(mBench.name()) << " (" << errorMessage << ')' << std::endl;
2439 } else {
2440 for (auto const& col : columns) {
2441 os << col.value();
2442 }
2443 os << "| ";
2444 auto showUnstable = isWarningsEnabled() && rErrorMedian >= 0.05;
2445 if (showUnstable) {
2446 os << ":wavy_dash: ";
2447 }
2448 os << fmt::MarkDownCode(mBench.name());
2449 if (showUnstable) {
2450 auto avgIters = d(mTotalNumIters) / d(mBench.epochs());
2451 // NOLINTNEXTLINE(bugprone-incorrect-roundings)
2452 auto suggestedIters = static_cast<uint64_t>(avgIters * 10 + 0.5);
2453
2454 os << " (Unstable with ~" << detail::fmt::Number(1, 1, avgIters)
2455 << " iters. Increase `minEpochIterations` to e.g. " << suggestedIters << ")";
2456 }
2457 os << std::endl;
2458 }
2459 }
2460 }
2461
2462 ANKERL_NANOBENCH(NODISCARD) bool isCloseEnoughForMeasurements(std::chrono::nanoseconds elapsed) const noexcept {
2463 return elapsed * 3 >= mTargetRuntimePerEpoch * 2;
2464 }
2465
2466 uint64_t mNumIters = 1; // NOLINT(misc-non-private-member-variables-in-classes)
2467 Bench const& mBench; // NOLINT(misc-non-private-member-variables-in-classes)
2468 std::chrono::nanoseconds mTargetRuntimePerEpoch{}; // NOLINT(misc-non-private-member-variables-in-classes)
2469 Result mResult; // NOLINT(misc-non-private-member-variables-in-classes)
2470 Rng mRng{123}; // NOLINT(misc-non-private-member-variables-in-classes)
2471 std::chrono::nanoseconds mTotalElapsed{}; // NOLINT(misc-non-private-member-variables-in-classes)
2472 uint64_t mTotalNumIters = 0; // NOLINT(misc-non-private-member-variables-in-classes)
2473 State mState = State::upscaling_runtime; // NOLINT(misc-non-private-member-variables-in-classes)
2474 };
2475 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
2476
2477 IterationLogic::IterationLogic(Bench const& bench)
2478 : mPimpl(new Impl(bench)) {}
2479
2480 IterationLogic::~IterationLogic() {
2481 delete mPimpl;
2482 }
2483
2484 uint64_t IterationLogic::numIters() const noexcept {
2485 ANKERL_NANOBENCH_LOG(mPimpl->mBench.name() << ": mNumIters=" << mPimpl->mNumIters);
2486 return mPimpl->mNumIters;
2487 }
2488
2489 void IterationLogic::add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept {
2490 mPimpl->add(elapsed, pc);
2491 }
2492
2493 void IterationLogic::moveResultTo(std::vector<Result>& results) noexcept {
2494 results.emplace_back(std::move(mPimpl->mResult));
2495 }
2496
2497 # if ANKERL_NANOBENCH(PERF_COUNTERS)
2498
2499 ANKERL_NANOBENCH(IGNORE_PADDED_PUSH)
2500 class LinuxPerformanceCounters {
2501 public:
2502 struct Target {
2503 Target(uint64_t* targetValue_, bool correctMeasuringOverhead_, bool correctLoopOverhead_)
2504 : targetValue(targetValue_)
2505 , correctMeasuringOverhead(correctMeasuringOverhead_)
2506 , correctLoopOverhead(correctLoopOverhead_) {}
2507
2508 uint64_t* targetValue{}; // NOLINT(misc-non-private-member-variables-in-classes)
2509 bool correctMeasuringOverhead{}; // NOLINT(misc-non-private-member-variables-in-classes)
2510 bool correctLoopOverhead{}; // NOLINT(misc-non-private-member-variables-in-classes)
2511 };
2512
2513 LinuxPerformanceCounters() = default;
2514 LinuxPerformanceCounters(LinuxPerformanceCounters const&) = delete;
2515 LinuxPerformanceCounters(LinuxPerformanceCounters&&) = delete;
2516 LinuxPerformanceCounters& operator=(LinuxPerformanceCounters const&) = delete;
2517 LinuxPerformanceCounters& operator=(LinuxPerformanceCounters&&) = delete;
2518 ~LinuxPerformanceCounters();
2519
2520 // quick operation
2521 inline void start() {}
2522
2523 inline void stop() {}
2524
2525 bool monitor(perf_sw_ids swId, Target target);
2526 bool monitor(perf_hw_id hwId, Target target);
2527
2528 ANKERL_NANOBENCH(NODISCARD) bool hasError() const noexcept {
2529 return mHasError;
2530 }
2531
2532 // Just reading data is faster than enable & disabling.
2533 // we subtract data ourselves.
2534 inline void beginMeasure() {
2535 if (mHasError) {
2536 return;
2537 }
2538
2539 // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg)
2540 mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP);
2541 if (mHasError) {
2542 return;
2543 }
2544
2545 // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg)
2546 mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP);
2547 }
2548
2549 inline void endMeasure() {
2550 if (mHasError) {
2551 return;
2552 }
2553
2554 // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg)
2555 mHasError = (-1 == ioctl(mFd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP));
2556 if (mHasError) {
2557 return;
2558 }
2559
2560 auto const numBytes = sizeof(uint64_t) * mCounters.size();
2561 auto ret = read(mFd, mCounters.data(), numBytes);
2562 mHasError = ret != static_cast<ssize_t>(numBytes);
2563 }
2564
2565 void updateResults(uint64_t numIters);
2566
2567 // rounded integer division
2568 template <typename T>
2569 static inline T divRounded(T a, T divisor) {
2570 return (a + divisor / 2) / divisor;
2571 }
2572
2573 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
2574 static inline uint32_t mix(uint32_t x) noexcept {
2575 x ^= x << 13U;
2576 x ^= x >> 17U;
2577 x ^= x << 5U;
2578 return x;
2579 }
2580
2581 template <typename Op>
2582 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
2583 void calibrate(Op&& op) {
2584 // clear current calibration data,
2585 for (auto& v : mCalibratedOverhead) {
2586 v = UINT64_C(0);
2587 }
2588
2589 // create new calibration data
2590 auto newCalibration = mCalibratedOverhead;
2591 for (auto& v : newCalibration) {
2592 v = (std::numeric_limits<uint64_t>::max)();
2593 }
2594 for (size_t iter = 0; iter < 100; ++iter) {
2595 beginMeasure();
2596 op();
2597 endMeasure();
2598 if (mHasError) {
2599 return;
2600 }
2601
2602 for (size_t i = 0; i < newCalibration.size(); ++i) {
2603 auto diff = mCounters[i];
2604 if (newCalibration[i] > diff) {
2605 newCalibration[i] = diff;
2606 }
2607 }
2608 }
2609
2610 mCalibratedOverhead = std::move(newCalibration);
2611
2612 {
2613 // calibrate loop overhead. For branches & instructions this makes sense, not so much for everything else like cycles.
2614 // marsaglia's xorshift: mov, sal/shr, xor. Times 3.
2615 // This has the nice property that the compiler doesn't seem to be able to optimize multiple calls any further.
2616 // see https://godbolt.org/z/49RVQ5
2617 uint64_t const numIters = 100000U + (std::random_device{}() & 3U);
2618 uint64_t n = numIters;
2619 uint32_t x = 1234567;
2620
2621 beginMeasure();
2622 while (n-- > 0) {
2623 x = mix(x);
2624 }
2625 endMeasure();
2626 detail::doNotOptimizeAway(x);
2627 auto measure1 = mCounters;
2628
2629 n = numIters;
2630 beginMeasure();
2631 while (n-- > 0) {
2632 // we now run *twice* so we can easily calculate the overhead
2633 x = mix(x);
2634 x = mix(x);
2635 }
2636 endMeasure();
2637 detail::doNotOptimizeAway(x);
2638 auto measure2 = mCounters;
2639
2640 for (size_t i = 0; i < mCounters.size(); ++i) {
2641 // factor 2 because we have two instructions per loop
2642 auto m1 = measure1[i] > mCalibratedOverhead[i] ? measure1[i] - mCalibratedOverhead[i] : 0;
2643 auto m2 = measure2[i] > mCalibratedOverhead[i] ? measure2[i] - mCalibratedOverhead[i] : 0;
2644 auto overhead = m1 * 2 > m2 ? m1 * 2 - m2 : 0;
2645
2646 mLoopOverhead[i] = divRounded(overhead, numIters);
2647 }
2648 }
2649 }
2650
2651 private:
2652 bool monitor(uint32_t type, uint64_t eventid, Target target);
2653
2654 std::map<uint64_t, Target> mIdToTarget{};
2655
2656 // start with minimum size of 3 for read_format
2657 std::vector<uint64_t> mCounters = std::vector<uint64_t>(3);
2658 std::vector<uint64_t> mCalibratedOverhead = std::vector<uint64_t>(3);
2659 std::vector<uint64_t> mLoopOverhead = std::vector<uint64_t>(3);
2660
2661 uint64_t mTimeEnabledNanos = 0;
2662 uint64_t mTimeRunningNanos = 0;
2663 int mFd = -1;
2664 bool mHasError = false;
2665 };
2666 ANKERL_NANOBENCH(IGNORE_PADDED_POP)
2667
2668 LinuxPerformanceCounters::~LinuxPerformanceCounters() {
2669 if (-1 != mFd) {
2670 close(mFd);
2671 }
2672 }
2673
2674 bool LinuxPerformanceCounters::monitor(perf_sw_ids swId, LinuxPerformanceCounters::Target target) {
2675 return monitor(PERF_TYPE_SOFTWARE, swId, target);
2676 }
2677
2678 bool LinuxPerformanceCounters::monitor(perf_hw_id hwId, LinuxPerformanceCounters::Target target) {
2679 return monitor(PERF_TYPE_HARDWARE, hwId, target);
2680 }
2681
2682 // overflow is ok, it's checked
2683 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
2684 void LinuxPerformanceCounters::updateResults(uint64_t numIters) {
2685 // clear old data
2686 for (auto& id_value : mIdToTarget) {
2687 *id_value.second.targetValue = UINT64_C(0);
2688 }
2689
2690 if (mHasError) {
2691 return;
2692 }
2693
2694 mTimeEnabledNanos = mCounters[1] - mCalibratedOverhead[1];
2695 mTimeRunningNanos = mCounters[2] - mCalibratedOverhead[2];
2696
2697 for (uint64_t i = 0; i < mCounters[0]; ++i) {
2698 auto idx = static_cast<size_t>(3 + i * 2 + 0);
2699 auto id = mCounters[idx + 1U];
2700
2701 auto it = mIdToTarget.find(id);
2702 if (it != mIdToTarget.end()) {
2703
2704 auto& tgt = it->second;
2705 *tgt.targetValue = mCounters[idx];
2706 if (tgt.correctMeasuringOverhead) {
2707 if (*tgt.targetValue >= mCalibratedOverhead[idx]) {
2708 *tgt.targetValue -= mCalibratedOverhead[idx];
2709 } else {
2710 *tgt.targetValue = 0U;
2711 }
2712 }
2713 if (tgt.correctLoopOverhead) {
2714 auto correctionVal = mLoopOverhead[idx] * numIters;
2715 if (*tgt.targetValue >= correctionVal) {
2716 *tgt.targetValue -= correctionVal;
2717 } else {
2718 *tgt.targetValue = 0U;
2719 }
2720 }
2721 }
2722 }
2723 }
2724
2725 bool LinuxPerformanceCounters::monitor(uint32_t type, uint64_t eventid, Target target) {
2726 *target.targetValue = (std::numeric_limits<uint64_t>::max)();
2727 if (mHasError) {
2728 return false;
2729 }
2730
2731 auto pea = perf_event_attr();
2732 std::memset(&pea, 0, sizeof(perf_event_attr));
2733 pea.type = type;
2734 pea.size = sizeof(perf_event_attr);
2735 pea.config = eventid;
2736 pea.disabled = 1; // start counter as disabled
2737 pea.exclude_kernel = 1;
2738 pea.exclude_hv = 1;
2739
2740 // NOLINTNEXTLINE(hicpp-signed-bitwise)
2741 pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
2742
2743 const int pid = 0; // the current process
2744 const int cpu = -1; // all CPUs
2745 # if defined(PERF_FLAG_FD_CLOEXEC) // since Linux 3.14
2746 const unsigned long flags = PERF_FLAG_FD_CLOEXEC;
2747 # else
2748 const unsigned long flags = 0;
2749 # endif
2750
2751 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
2752 auto fd = static_cast<int>(syscall(__NR_perf_event_open, &pea, pid, cpu, mFd, flags));
2753 if (-1 == fd) {
2754 return false;
2755 }
2756 if (-1 == mFd) {
2757 // first call: set to fd, and use this from now on
2758 mFd = fd;
2759 }
2760 uint64_t id = 0;
2761 // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg)
2762 if (-1 == ioctl(fd, PERF_EVENT_IOC_ID, &id)) {
2763 // couldn't get id
2764 return false;
2765 }
2766
2767 // insert into map, rely on the fact that map's references are constant.
2768 mIdToTarget.emplace(id, target);
2769
2770 // prepare readformat with the correct size (after the insert)
2771 auto size = 3 + 2 * mIdToTarget.size();
2772 mCounters.resize(size);
2773 mCalibratedOverhead.resize(size);
2774 mLoopOverhead.resize(size);
2775
2776 return true;
2777 }
2778
2779 PerformanceCounters::PerformanceCounters()
2780 : mPc(new LinuxPerformanceCounters())
2781 , mVal()
2782 , mHas() {
2783
2784 // HW events
2785 mHas.cpuCycles = mPc->monitor(PERF_COUNT_HW_REF_CPU_CYCLES, LinuxPerformanceCounters::Target(&mVal.cpuCycles, true, false));
2786 if (!mHas.cpuCycles) {
2787 // Fallback to cycles counter, reference cycles not available in many systems.
2788 mHas.cpuCycles = mPc->monitor(PERF_COUNT_HW_CPU_CYCLES, LinuxPerformanceCounters::Target(&mVal.cpuCycles, true, false));
2789 }
2790 mHas.instructions = mPc->monitor(PERF_COUNT_HW_INSTRUCTIONS, LinuxPerformanceCounters::Target(&mVal.instructions, true, true));
2791 mHas.branchInstructions =
2792 mPc->monitor(PERF_COUNT_HW_BRANCH_INSTRUCTIONS, LinuxPerformanceCounters::Target(&mVal.branchInstructions, true, false));
2793 mHas.branchMisses = mPc->monitor(PERF_COUNT_HW_BRANCH_MISSES, LinuxPerformanceCounters::Target(&mVal.branchMisses, true, false));
2794 // mHas.branchMisses = false;
2795
2796 // SW events
2797 mHas.pageFaults = mPc->monitor(PERF_COUNT_SW_PAGE_FAULTS, LinuxPerformanceCounters::Target(&mVal.pageFaults, true, false));
2798 mHas.contextSwitches =
2799 mPc->monitor(PERF_COUNT_SW_CONTEXT_SWITCHES, LinuxPerformanceCounters::Target(&mVal.contextSwitches, true, false));
2800
2801 mPc->start();
2802 mPc->calibrate([] {
2803 auto before = ankerl::nanobench::Clock::now();
2804 auto after = ankerl::nanobench::Clock::now();
2805 (void)before;
2806 (void)after;
2807 });
2808
2809 if (mPc->hasError()) {
2810 // something failed, don't monitor anything.
2811 mHas = PerfCountSet<bool>{};
2812 }
2813 }
2814
2815 PerformanceCounters::~PerformanceCounters() {
2816 // no need to check for nullptr, delete nullptr has no effect
2817 delete mPc;
2818 }
2819
2820 void PerformanceCounters::beginMeasure() {
2821 mPc->beginMeasure();
2822 }
2823
2824 void PerformanceCounters::endMeasure() {
2825 mPc->endMeasure();
2826 }
2827
2828 void PerformanceCounters::updateResults(uint64_t numIters) {
2829 mPc->updateResults(numIters);
2830 }
2831
2832 # else
2833
2834 PerformanceCounters::PerformanceCounters() = default;
2835 PerformanceCounters::~PerformanceCounters() = default;
2836 void PerformanceCounters::beginMeasure() {}
2837 void PerformanceCounters::endMeasure() {}
2838 void PerformanceCounters::updateResults(uint64_t) {}
2839
2840 # endif
2841
2842 ANKERL_NANOBENCH(NODISCARD) PerfCountSet<uint64_t> const& PerformanceCounters::val() const noexcept {
2843 return mVal;
2844 }
2845 ANKERL_NANOBENCH(NODISCARD) PerfCountSet<bool> const& PerformanceCounters::has() const noexcept {
2846 return mHas;
2847 }
2848
2849 // formatting utilities
2850 namespace fmt {
2851
2852 // adds thousands separator to numbers
2853 NumSep::NumSep(char sep)
2854 : mSep(sep) {}
2855
2856 char NumSep::do_thousands_sep() const {
2857 return mSep;
2858 }
2859
2860 std::string NumSep::do_grouping() const {
2861 return "\003";
2862 }
2863
2864 // RAII to save & restore a stream's state
2865 StreamStateRestorer::StreamStateRestorer(std::ostream& s)
2866 : mStream(s)
2867 , mLocale(s.getloc())
2868 , mPrecision(s.precision())
2869 , mWidth(s.width())
2870 , mFill(s.fill())
2871 , mFmtFlags(s.flags()) {}
2872
2873 StreamStateRestorer::~StreamStateRestorer() {
2874 restore();
2875 }
2876
2877 // sets back all stream info that we remembered at construction
2878 void StreamStateRestorer::restore() {
2879 mStream.imbue(mLocale);
2880 mStream.precision(mPrecision);
2881 mStream.width(mWidth);
2882 mStream.fill(mFill);
2883 mStream.flags(mFmtFlags);
2884 }
2885
2886 Number::Number(int width, int precision, int64_t value)
2887 : mWidth(width)
2888 , mPrecision(precision)
2889 , mValue(d(value)) {}
2890
2891 Number::Number(int width, int precision, double value)
2892 : mWidth(width)
2893 , mPrecision(precision)
2894 , mValue(value) {}
2895
2896 std::ostream& Number::write(std::ostream& os) const {
2897 StreamStateRestorer const restorer(os);
2898 os.imbue(std::locale(os.getloc(), new NumSep(',')));
2899 os << std::setw(mWidth) << std::setprecision(mPrecision) << std::fixed << mValue;
2900 return os;
2901 }
2902
2903 std::string Number::to_s() const {
2904 std::stringstream ss;
2905 write(ss);
2906 return ss.str();
2907 }
2908
2909 std::string to_s(uint64_t n) {
2910 std::string str;
2911 do {
2912 str += static_cast<char>('0' + static_cast<char>(n % 10));
2913 n /= 10;
2914 } while (n != 0);
2915 std::reverse(str.begin(), str.end());
2916 return str;
2917 }
2918
2919 std::ostream& operator<<(std::ostream& os, Number const& n) {
2920 return n.write(os);
2921 }
2922
2923 MarkDownColumn::MarkDownColumn(int w, int prec, std::string tit, std::string suff, double val) noexcept
2924 : mWidth(w)
2925 , mPrecision(prec)
2926 , mTitle(std::move(tit))
2927 , mSuffix(std::move(suff))
2928 , mValue(val) {}
2929
2930 std::string MarkDownColumn::title() const {
2931 std::stringstream ss;
2932 ss << '|' << std::setw(mWidth - 2) << std::right << mTitle << ' ';
2933 return ss.str();
2934 }
2935
2936 std::string MarkDownColumn::separator() const {
2937 std::string sep(static_cast<size_t>(mWidth), '-');
2938 sep.front() = '|';
2939 sep.back() = ':';
2940 return sep;
2941 }
2942
2943 std::string MarkDownColumn::invalid() const {
2944 std::string sep(static_cast<size_t>(mWidth), ' ');
2945 sep.front() = '|';
2946 sep[sep.size() - 2] = '-';
2947 return sep;
2948 }
2949
2950 std::string MarkDownColumn::value() const {
2951 std::stringstream ss;
2952 auto width = mWidth - 2 - static_cast<int>(mSuffix.size());
2953 ss << '|' << Number(width, mPrecision, mValue) << mSuffix << ' ';
2954 return ss.str();
2955 }
2956
2957 // Formats any text as markdown code, escaping backticks.
2958 MarkDownCode::MarkDownCode(std::string const& what) {
2959 mWhat.reserve(what.size() + 2);
2960 mWhat.push_back('`');
2961 for (char const c : what) {
2962 mWhat.push_back(c);
2963 if ('`' == c) {
2964 mWhat.push_back('`');
2965 }
2966 }
2967 mWhat.push_back('`');
2968 }
2969
2970 std::ostream& MarkDownCode::write(std::ostream& os) const {
2971 return os << mWhat;
2972 }
2973
2974 std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode) {
2975 return mdCode.write(os);
2976 }
2977 } // namespace fmt
2978 } // namespace detail
2979
2980 // provide implementation here so it's only generated once
2981 Config::Config() = default;
2982 Config::~Config() = default;
2983 Config& Config::operator=(Config const&) = default;
2984 Config& Config::operator=(Config&&) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)) = default;
2985 Config::Config(Config const&) = default;
2986 Config::Config(Config&&) noexcept = default;
2987
2988 // provide implementation here so it's only generated once
2989 Result::~Result() = default;
2990 Result& Result::operator=(Result const&) = default;
2991 Result& Result::operator=(Result&&) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)) = default;
2992 Result::Result(Result const&) = default;
2993 Result::Result(Result&&) noexcept = default;
2994
2995 namespace detail {
2996 template <typename T>
2997 inline constexpr typename std::underlying_type<T>::type u(T val) noexcept {
2998 return static_cast<typename std::underlying_type<T>::type>(val);
2999 }
3000 } // namespace detail
3001
3002 // Result returned after a benchmark has finished. Can be used as a baseline for relative().
3003 Result::Result(Config benchmarkConfig)
3004 : mConfig(std::move(benchmarkConfig))
3005 , mNameToMeasurements{detail::u(Result::Measure::_size)} {}
3006
3007 void Result::add(Clock::duration totalElapsed, uint64_t iters, detail::PerformanceCounters const& pc) {
3008 using detail::d;
3009 using detail::u;
3010
3011 double const dIters = d(iters);
3012 mNameToMeasurements[u(Result::Measure::iterations)].push_back(dIters);
3013
3014 mNameToMeasurements[u(Result::Measure::elapsed)].push_back(d(totalElapsed) / dIters);
3015 if (pc.has().pageFaults) {
3016 mNameToMeasurements[u(Result::Measure::pagefaults)].push_back(d(pc.val().pageFaults) / dIters);
3017 }
3018 if (pc.has().cpuCycles) {
3019 mNameToMeasurements[u(Result::Measure::cpucycles)].push_back(d(pc.val().cpuCycles) / dIters);
3020 }
3021 if (pc.has().contextSwitches) {
3022 mNameToMeasurements[u(Result::Measure::contextswitches)].push_back(d(pc.val().contextSwitches) / dIters);
3023 }
3024 if (pc.has().instructions) {
3025 mNameToMeasurements[u(Result::Measure::instructions)].push_back(d(pc.val().instructions) / dIters);
3026 }
3027 if (pc.has().branchInstructions) {
3028 double branchInstructions = 0.0;
3029 // correcting branches: remove branch introduced by the while (...) loop for each iteration.
3030 if (pc.val().branchInstructions > iters + 1U) {
3031 branchInstructions = d(pc.val().branchInstructions - (iters + 1U));
3032 }
3033 mNameToMeasurements[u(Result::Measure::branchinstructions)].push_back(branchInstructions / dIters);
3034
3035 if (pc.has().branchMisses) {
3036 // correcting branch misses
3037 double branchMisses = d(pc.val().branchMisses);
3038 if (branchMisses > branchInstructions) {
3039 // can't have branch misses when there were branches...
3040 branchMisses = branchInstructions;
3041 }
3042
3043 // assuming at least one missed branch for the loop
3044 branchMisses -= 1.0;
3045 if (branchMisses < 1.0) {
3046 branchMisses = 1.0;
3047 }
3048 mNameToMeasurements[u(Result::Measure::branchmisses)].push_back(branchMisses / dIters);
3049 }
3050 }
3051 }
3052
3053 Config const& Result::config() const noexcept {
3054 return mConfig;
3055 }
3056
3057 inline double calcMedian(std::vector<double>& data) {
3058 if (data.empty()) {
3059 return 0.0;
3060 }
3061 std::sort(data.begin(), data.end());
3062
3063 auto midIdx = data.size() / 2U;
3064 if (1U == (data.size() & 1U)) {
3065 return data[midIdx];
3066 }
3067 return (data[midIdx - 1U] + data[midIdx]) / 2U;
3068 }
3069
3070 double Result::median(Measure m) const {
3071 // create a copy so we can sort
3072 auto data = mNameToMeasurements[detail::u(m)];
3073 return calcMedian(data);
3074 }
3075
3076 double Result::average(Measure m) const {
3077 using detail::d;
3078 auto const& data = mNameToMeasurements[detail::u(m)];
3079 if (data.empty()) {
3080 return 0.0;
3081 }
3082
3083 // create a copy so we can sort
3084 return sum(m) / d(data.size());
3085 }
3086
3087 double Result::medianAbsolutePercentError(Measure m) const {
3088 // create copy
3089 auto data = mNameToMeasurements[detail::u(m)];
3090
3091 // calculates MdAPE which is the median of percentage error
3092 // see https://support.numxl.com/hc/en-us/articles/115001223503-MdAPE-Median-Absolute-Percentage-Error
3093 auto med = calcMedian(data);
3094
3095 // transform the data to absolute error
3096 for (auto& x : data) {
3097 x = (x - med) / x;
3098 if (x < 0) {
3099 x = -x;
3100 }
3101 }
3102 return calcMedian(data);
3103 }
3104
3105 double Result::sum(Measure m) const noexcept {
3106 auto const& data = mNameToMeasurements[detail::u(m)];
3107 return std::accumulate(data.begin(), data.end(), 0.0);
3108 }
3109
3110 double Result::sumProduct(Measure m1, Measure m2) const noexcept {
3111 auto const& data1 = mNameToMeasurements[detail::u(m1)];
3112 auto const& data2 = mNameToMeasurements[detail::u(m2)];
3113
3114 if (data1.size() != data2.size()) {
3115 return 0.0;
3116 }
3117
3118 double result = 0.0;
3119 for (size_t i = 0, s = data1.size(); i != s; ++i) {
3120 result += data1[i] * data2[i];
3121 }
3122 return result;
3123 }
3124
3125 bool Result::has(Measure m) const noexcept {
3126 return !mNameToMeasurements[detail::u(m)].empty();
3127 }
3128
3129 double Result::get(size_t idx, Measure m) const {
3130 auto const& data = mNameToMeasurements[detail::u(m)];
3131 return data.at(idx);
3132 }
3133
3134 bool Result::empty() const noexcept {
3135 return 0U == size();
3136 }
3137
3138 size_t Result::size() const noexcept {
3139 auto const& data = mNameToMeasurements[detail::u(Measure::elapsed)];
3140 return data.size();
3141 }
3142
3143 double Result::minimum(Measure m) const noexcept {
3144 auto const& data = mNameToMeasurements[detail::u(m)];
3145 if (data.empty()) {
3146 return 0.0;
3147 }
3148
3149 // here its save to assume that at least one element is there
3150 return *std::min_element(data.begin(), data.end());
3151 }
3152
3153 double Result::maximum(Measure m) const noexcept {
3154 auto const& data = mNameToMeasurements[detail::u(m)];
3155 if (data.empty()) {
3156 return 0.0;
3157 }
3158
3159 // here its save to assume that at least one element is there
3160 return *std::max_element(data.begin(), data.end());
3161 }
3162
3163 std::string const& Result::context(char const* variableName) const {
3164 return mConfig.mContext.at(variableName);
3165 }
3166
3167 std::string const& Result::context(std::string const& variableName) const {
3168 return mConfig.mContext.at(variableName);
3169 }
3170
3171 Result::Measure Result::fromString(std::string const& str) {
3172 if (str == "elapsed") {
3173 return Measure::elapsed;
3174 }
3175 if (str == "iterations") {
3176 return Measure::iterations;
3177 }
3178 if (str == "pagefaults") {
3179 return Measure::pagefaults;
3180 }
3181 if (str == "cpucycles") {
3182 return Measure::cpucycles;
3183 }
3184 if (str == "contextswitches") {
3185 return Measure::contextswitches;
3186 }
3187 if (str == "instructions") {
3188 return Measure::instructions;
3189 }
3190 if (str == "branchinstructions") {
3191 return Measure::branchinstructions;
3192 }
3193 if (str == "branchmisses") {
3194 return Measure::branchmisses;
3195 }
3196 // not found, return _size
3197 return Measure::_size;
3198 }
3199
3200 // Configuration of a microbenchmark.
3201 Bench::Bench() {
3202 mConfig.mOut = &std::cout;
3203 }
3204
3205 Bench::Bench(Bench&&) noexcept = default;
3206 Bench& Bench::operator=(Bench&&) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)) = default;
3207 Bench::Bench(Bench const&) = default;
3208 Bench& Bench::operator=(Bench const&) = default;
3209 Bench::~Bench() noexcept = default;
3210
3211 double Bench::batch() const noexcept {
3212 return mConfig.mBatch;
3213 }
3214
3215 double Bench::complexityN() const noexcept {
3216 return mConfig.mComplexityN;
3217 }
3218
3219 // Set a baseline to compare it to. 100% it is exactly as fast as the baseline, >100% means it is faster than the baseline, <100%
3220 // means it is slower than the baseline.
3221 Bench& Bench::relative(bool isRelativeEnabled) noexcept {
3222 mConfig.mIsRelative = isRelativeEnabled;
3223 return *this;
3224 }
3225 bool Bench::relative() const noexcept {
3226 return mConfig.mIsRelative;
3227 }
3228
3229 Bench& Bench::performanceCounters(bool showPerformanceCounters) noexcept {
3230 mConfig.mShowPerformanceCounters = showPerformanceCounters;
3231 return *this;
3232 }
3233 bool Bench::performanceCounters() const noexcept {
3234 return mConfig.mShowPerformanceCounters;
3235 }
3236
3237 // Operation unit. Defaults to "op", could be e.g. "byte" for string processing.
3238 // If u differs from currently set unit, the stored results will be cleared.
3239 // Use singular (byte, not bytes).
3240 Bench& Bench::unit(char const* u) {
3241 if (u != mConfig.mUnit) {
3242 mResults.clear();
3243 }
3244 mConfig.mUnit = u;
3245 return *this;
3246 }
3247
3248 Bench& Bench::unit(std::string const& u) {
3249 return unit(u.c_str());
3250 }
3251
3252 std::string const& Bench::unit() const noexcept {
3253 return mConfig.mUnit;
3254 }
3255
3256 Bench& Bench::timeUnit(std::chrono::duration<double> const& tu, std::string const& tuName) {
3257 mConfig.mTimeUnit = tu;
3258 mConfig.mTimeUnitName = tuName;
3259 return *this;
3260 }
3261
3262 std::string const& Bench::timeUnitName() const noexcept {
3263 return mConfig.mTimeUnitName;
3264 }
3265
3266 std::chrono::duration<double> const& Bench::timeUnit() const noexcept {
3267 return mConfig.mTimeUnit;
3268 }
3269
3270 // If benchmarkTitle differs from currently set title, the stored results will be cleared.
3271 Bench& Bench::title(const char* benchmarkTitle) {
3272 if (benchmarkTitle != mConfig.mBenchmarkTitle) {
3273 mResults.clear();
3274 }
3275 mConfig.mBenchmarkTitle = benchmarkTitle;
3276 return *this;
3277 }
3278 Bench& Bench::title(std::string const& benchmarkTitle) {
3279 if (benchmarkTitle != mConfig.mBenchmarkTitle) {
3280 mResults.clear();
3281 }
3282 mConfig.mBenchmarkTitle = benchmarkTitle;
3283 return *this;
3284 }
3285
3286 std::string const& Bench::title() const noexcept {
3287 return mConfig.mBenchmarkTitle;
3288 }
3289
3290 Bench& Bench::name(const char* benchmarkName) {
3291 mConfig.mBenchmarkName = benchmarkName;
3292 return *this;
3293 }
3294
3295 Bench& Bench::name(std::string const& benchmarkName) {
3296 mConfig.mBenchmarkName = benchmarkName;
3297 return *this;
3298 }
3299
3300 std::string const& Bench::name() const noexcept {
3301 return mConfig.mBenchmarkName;
3302 }
3303
3304 Bench& Bench::context(char const* variableName, char const* variableValue) {
3305 mConfig.mContext[variableName] = variableValue;
3306 return *this;
3307 }
3308
3309 Bench& Bench::context(std::string const& variableName, std::string const& variableValue) {
3310 mConfig.mContext[variableName] = variableValue;
3311 return *this;
3312 }
3313
3314 Bench& Bench::clearContext() {
3315 mConfig.mContext.clear();
3316 return *this;
3317 }
3318
3319 // Number of epochs to evaluate. The reported result will be the median of evaluation of each epoch.
3320 Bench& Bench::epochs(size_t numEpochs) noexcept {
3321 mConfig.mNumEpochs = numEpochs;
3322 return *this;
3323 }
3324 size_t Bench::epochs() const noexcept {
3325 return mConfig.mNumEpochs;
3326 }
3327
3328 // Desired evaluation time is a multiple of clock resolution. Default is to be 1000 times above this measurement precision.
3329 Bench& Bench::clockResolutionMultiple(size_t multiple) noexcept {
3330 mConfig.mClockResolutionMultiple = multiple;
3331 return *this;
3332 }
3333 size_t Bench::clockResolutionMultiple() const noexcept {
3334 return mConfig.mClockResolutionMultiple;
3335 }
3336
3337 // Sets the maximum time each epoch should take. Default is 100ms.
3338 Bench& Bench::maxEpochTime(std::chrono::nanoseconds t) noexcept {
3339 mConfig.mMaxEpochTime = t;
3340 return *this;
3341 }
3342 std::chrono::nanoseconds Bench::maxEpochTime() const noexcept {
3343 return mConfig.mMaxEpochTime;
3344 }
3345
3346 // Sets the maximum time each epoch should take. Default is 100ms.
3347 Bench& Bench::minEpochTime(std::chrono::nanoseconds t) noexcept {
3348 mConfig.mMinEpochTime = t;
3349 return *this;
3350 }
3351 std::chrono::nanoseconds Bench::minEpochTime() const noexcept {
3352 return mConfig.mMinEpochTime;
3353 }
3354
3355 Bench& Bench::minEpochIterations(uint64_t numIters) noexcept {
3356 mConfig.mMinEpochIterations = (numIters == 0) ? 1 : numIters;
3357 return *this;
3358 }
3359 uint64_t Bench::minEpochIterations() const noexcept {
3360 return mConfig.mMinEpochIterations;
3361 }
3362
3363 Bench& Bench::epochIterations(uint64_t numIters) noexcept {
3364 mConfig.mEpochIterations = numIters;
3365 return *this;
3366 }
3367 uint64_t Bench::epochIterations() const noexcept {
3368 return mConfig.mEpochIterations;
3369 }
3370
3371 Bench& Bench::warmup(uint64_t numWarmupIters) noexcept {
3372 mConfig.mWarmup = numWarmupIters;
3373 return *this;
3374 }
3375 uint64_t Bench::warmup() const noexcept {
3376 return mConfig.mWarmup;
3377 }
3378
3379 Bench& Bench::config(Config const& benchmarkConfig) {
3380 mConfig = benchmarkConfig;
3381 return *this;
3382 }
3383 Config const& Bench::config() const noexcept {
3384 return mConfig;
3385 }
3386
3387 Bench& Bench::output(std::ostream* outstream) noexcept {
3388 mConfig.mOut = outstream;
3389 return *this;
3390 }
3391
3392 ANKERL_NANOBENCH(NODISCARD) std::ostream* Bench::output() const noexcept {
3393 return mConfig.mOut;
3394 }
3395
3396 std::vector<Result> const& Bench::results() const noexcept {
3397 return mResults;
3398 }
3399
3400 Bench& Bench::render(char const* templateContent, std::ostream& os) {
3401 ::ankerl::nanobench::render(templateContent, *this, os);
3402 return *this;
3403 }
3404
3405 Bench& Bench::render(std::string const& templateContent, std::ostream& os) {
3406 ::ankerl::nanobench::render(templateContent, *this, os);
3407 return *this;
3408 }
3409
3410 std::vector<BigO> Bench::complexityBigO() const {
3411 std::vector<BigO> bigOs;
3412 auto rangeMeasure = BigO::collectRangeMeasure(mResults);
3413 bigOs.emplace_back("O(1)", rangeMeasure, [](double) {
3414 return 1.0;
3415 });
3416 bigOs.emplace_back("O(n)", rangeMeasure, [](double n) {
3417 return n;
3418 });
3419 bigOs.emplace_back("O(log n)", rangeMeasure, [](double n) {
3420 return std::log2(n);
3421 });
3422 bigOs.emplace_back("O(n log n)", rangeMeasure, [](double n) {
3423 return n * std::log2(n);
3424 });
3425 bigOs.emplace_back("O(n^2)", rangeMeasure, [](double n) {
3426 return n * n;
3427 });
3428 bigOs.emplace_back("O(n^3)", rangeMeasure, [](double n) {
3429 return n * n * n;
3430 });
3431 std::sort(bigOs.begin(), bigOs.end());
3432 return bigOs;
3433 }
3434
3435 Rng::Rng()
3436 : mX(0)
3437 , mY(0) {
3438 std::random_device rd;
3439 std::uniform_int_distribution<uint64_t> dist;
3440 do {
3441 mX = dist(rd);
3442 mY = dist(rd);
3443 } while (mX == 0 && mY == 0);
3444 }
3445
3446 ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined")
3447 uint64_t splitMix64(uint64_t& state) noexcept {
3448 uint64_t z = (state += UINT64_C(0x9e3779b97f4a7c15));
3449 z = (z ^ (z >> 30U)) * UINT64_C(0xbf58476d1ce4e5b9);
3450 z = (z ^ (z >> 27U)) * UINT64_C(0x94d049bb133111eb);
3451 return z ^ (z >> 31U);
3452 }
3453
3454 // Seeded as described in romu paper (update april 2020)
3455 Rng::Rng(uint64_t seed) noexcept
3456 : mX(splitMix64(seed))
3457 , mY(splitMix64(seed)) {
3458 for (size_t i = 0; i < 10; ++i) {
3459 operator()();
3460 }
3461 }
3462
3463 // only internally used to copy the RNG.
3464 Rng::Rng(uint64_t x, uint64_t y) noexcept
3465 : mX(x)
3466 , mY(y) {}
3467
3468 Rng Rng::copy() const noexcept {
3469 return Rng{mX, mY};
3470 }
3471
3472 Rng::Rng(std::vector<uint64_t> const& data)
3473 : mX(0)
3474 , mY(0) {
3475 if (data.size() != 2) {
3476 throw std::runtime_error("ankerl::nanobench::Rng::Rng: needed exactly 2 entries in data, but got " +
3477 detail::fmt::to_s(data.size()));
3478 }
3479 mX = data[0];
3480 mY = data[1];
3481 }
3482
3483 std::vector<uint64_t> Rng::state() const {
3484 std::vector<uint64_t> data(2);
3485 data[0] = mX;
3486 data[1] = mY;
3487 return data;
3488 }
3489
3490 BigO::RangeMeasure BigO::collectRangeMeasure(std::vector<Result> const& results) {
3491 BigO::RangeMeasure rangeMeasure;
3492 for (auto const& result : results) {
3493 if (result.config().mComplexityN > 0.0) {
3494 rangeMeasure.emplace_back(result.config().mComplexityN, result.median(Result::Measure::elapsed));
3495 }
3496 }
3497 return rangeMeasure;
3498 }
3499
3500 BigO::BigO(std::string bigOName, RangeMeasure const& rangeMeasure)
3501 : mName(std::move(bigOName)) {
3502
3503 // estimate the constant factor
3504 double sumRangeMeasure = 0.0;
3505 double sumRangeRange = 0.0;
3506
3507 for (const auto& rm : rangeMeasure) {
3508 sumRangeMeasure += rm.first * rm.second;
3509 sumRangeRange += rm.first * rm.first;
3510 }
3511 mConstant = sumRangeMeasure / sumRangeRange;
3512
3513 // calculate root mean square
3514 double err = 0.0;
3515 double sumMeasure = 0.0;
3516 for (const auto& rm : rangeMeasure) {
3517 auto diff = mConstant * rm.first - rm.second;
3518 err += diff * diff;
3519
3520 sumMeasure += rm.second;
3521 }
3522
3523 auto n = detail::d(rangeMeasure.size());
3524 auto mean = sumMeasure / n;
3525 mNormalizedRootMeanSquare = std::sqrt(err / n) / mean;
3526 }
3527
3528 BigO::BigO(const char* bigOName, RangeMeasure const& rangeMeasure)
3529 : BigO(std::string(bigOName), rangeMeasure) {}
3530
3531 std::string const& BigO::name() const noexcept {
3532 return mName;
3533 }
3534
3535 double BigO::constant() const noexcept {
3536 return mConstant;
3537 }
3538
3539 double BigO::normalizedRootMeanSquare() const noexcept {
3540 return mNormalizedRootMeanSquare;
3541 }
3542
3543 bool BigO::operator<(BigO const& other) const noexcept {
3544 return std::tie(mNormalizedRootMeanSquare, mName) < std::tie(other.mNormalizedRootMeanSquare, other.mName);
3545 }
3546
3547 std::ostream& operator<<(std::ostream& os, BigO const& bigO) {
3548 return os << bigO.constant() << " * " << bigO.name() << ", rms=" << bigO.normalizedRootMeanSquare();
3549 }
3550
3551 std::ostream& operator<<(std::ostream& os, std::vector<ankerl::nanobench::BigO> const& bigOs) {
3552 detail::fmt::StreamStateRestorer const restorer(os);
3553 os << std::endl << "| coefficient | err% | complexity" << std::endl << "|--------------:|-------:|------------" << std::endl;
3554 for (auto const& bigO : bigOs) {
3555 os << "|" << std::setw(14) << std::setprecision(7) << std::scientific << bigO.constant() << " ";
3556 os << "|" << detail::fmt::Number(6, 1, bigO.normalizedRootMeanSquare() * 100.0) << "% ";
3557 os << "| " << bigO.name();
3558 os << std::endl;
3559 }
3560 return os;
3561 }
3562
3563 } // namespace nanobench
3564 } // namespace ankerl
3565
3566 #endif // ANKERL_NANOBENCH_IMPLEMENT
3567 #endif // ANKERL_NANOBENCH_H_INCLUDED
3568