blob: c2b650d191b7ccd3dbfdc02a7eb9c351afb021d7 [file] [log] [blame]
Austin Schuh36244a12019-09-21 17:52:38 -07001// Copyright 2017 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef ABSL_RANDOM_INTERNAL_NANOBENCHMARK_H_
16#define ABSL_RANDOM_INTERNAL_NANOBENCHMARK_H_
17
18// Benchmarks functions of a single integer argument with realistic branch
19// prediction hit rates. Uses a robust estimator to summarize the measurements.
20// The precision is about 0.2%.
21//
22// Examples: see nanobenchmark_test.cc.
23//
24// Background: Microbenchmarks such as http://github.com/google/benchmark
25// can measure elapsed times on the order of a microsecond. Shorter functions
26// are typically measured by repeating them thousands of times and dividing
27// the total elapsed time by this count. Unfortunately, repetition (especially
28// with the same input parameter!) influences the runtime. In time-critical
29// code, it is reasonable to expect warm instruction/data caches and TLBs,
30// but a perfect record of which branches will be taken is unrealistic.
31// Unless the application also repeatedly invokes the measured function with
32// the same parameter, the benchmark is measuring something very different -
33// a best-case result, almost as if the parameter were made a compile-time
34// constant. This may lead to erroneous conclusions about branch-heavy
35// algorithms outperforming branch-free alternatives.
36//
37// Our approach differs in three ways. Adding fences to the timer functions
38// reduces variability due to instruction reordering, improving the timer
39// resolution to about 40 CPU cycles. However, shorter functions must still
40// be invoked repeatedly. For more realistic branch prediction performance,
41// we vary the input parameter according to a user-specified distribution.
42// Thus, instead of VaryInputs(Measure(Repeat(func))), we change the
43// loop nesting to Measure(Repeat(VaryInputs(func))). We also estimate the
44// central tendency of the measurement samples with the "half sample mode",
45// which is more robust to outliers and skewed data than the mean or median.
46
47// NOTE: for compatibility with multiple translation units compiled with
48// distinct flags, avoid #including headers that define functions.
49
50#include <stddef.h>
51#include <stdint.h>
52
53namespace absl {
54namespace random_internal_nanobenchmark {
55
56// Input influencing the function being measured (e.g. number of bytes to copy).
57using FuncInput = size_t;
58
59// "Proof of work" returned by Func to ensure the compiler does not elide it.
60using FuncOutput = uint64_t;
61
62// Function to measure: either 1) a captureless lambda or function with two
63// arguments or 2) a lambda with capture, in which case the first argument
64// is reserved for use by MeasureClosure.
65using Func = FuncOutput (*)(const void*, FuncInput);
66
67// Internal parameters that determine precision/resolution/measuring time.
68struct Params {
69 // For measuring timer overhead/resolution. Used in a nested loop =>
70 // quadratic time, acceptable because we know timer overhead is "low".
71 // constexpr because this is used to define array bounds.
72 static constexpr size_t kTimerSamples = 256;
73
74 // Best-case precision, expressed as a divisor of the timer resolution.
75 // Larger => more calls to Func and higher precision.
76 size_t precision_divisor = 1024;
77
78 // Ratio between full and subset input distribution sizes. Cannot be less
79 // than 2; larger values increase measurement time but more faithfully
80 // model the given input distribution.
81 size_t subset_ratio = 2;
82
83 // Together with the estimated Func duration, determines how many times to
84 // call Func before checking the sample variability. Larger values increase
85 // measurement time, memory/cache use and precision.
86 double seconds_per_eval = 4E-3;
87
88 // The minimum number of samples before estimating the central tendency.
89 size_t min_samples_per_eval = 7;
90
91 // The mode is better than median for estimating the central tendency of
92 // skewed/fat-tailed distributions, but it requires sufficient samples
93 // relative to the width of half-ranges.
94 size_t min_mode_samples = 64;
95
96 // Maximum permissible variability (= median absolute deviation / center).
97 double target_rel_mad = 0.002;
98
99 // Abort after this many evals without reaching target_rel_mad. This
100 // prevents infinite loops.
101 size_t max_evals = 9;
102
103 // Retry the measure loop up to this many times.
104 size_t max_measure_retries = 2;
105
106 // Whether to print additional statistics to stdout.
107 bool verbose = true;
108};
109
110// Measurement result for each unique input.
111struct Result {
112 FuncInput input;
113
114 // Robust estimate (mode or median) of duration.
115 float ticks;
116
117 // Measure of variability (median absolute deviation relative to "ticks").
118 float variability;
119};
120
121// Ensures the thread is running on the specified cpu, and no others.
122// Reduces noise due to desynchronized socket RDTSC and context switches.
123// If "cpu" is negative, pin to the currently running core.
124void PinThreadToCPU(const int cpu = -1);
125
126// Returns tick rate, useful for converting measurements to seconds. Invariant
127// means the tick counter frequency is independent of CPU throttling or sleep.
128// This call may be expensive, callers should cache the result.
129double InvariantTicksPerSecond();
130
131// Precisely measures the number of ticks elapsed when calling "func" with the
132// given inputs, shuffled to ensure realistic branch prediction hit rates.
133//
134// "func" returns a 'proof of work' to ensure its computations are not elided.
135// "arg" is passed to Func, or reserved for internal use by MeasureClosure.
136// "inputs" is an array of "num_inputs" (not necessarily unique) arguments to
137// "func". The values should be chosen to maximize coverage of "func". This
138// represents a distribution, so a value's frequency should reflect its
139// probability in the real application. Order does not matter; for example, a
140// uniform distribution over [0, 4) could be represented as {3,0,2,1}.
141// Returns how many Result were written to "results": one per unique input, or
142// zero if the measurement failed (an error message goes to stderr).
143size_t Measure(const Func func, const void* arg, const FuncInput* inputs,
144 const size_t num_inputs, Result* results,
145 const Params& p = Params());
146
147// Calls operator() of the given closure (lambda function).
148template <class Closure>
149static FuncOutput CallClosure(const void* f, const FuncInput input) {
150 return (*reinterpret_cast<const Closure*>(f))(input);
151}
152
153// Same as Measure, except "closure" is typically a lambda function of
154// FuncInput -> FuncOutput with a capture list.
155template <class Closure>
156static inline size_t MeasureClosure(const Closure& closure,
157 const FuncInput* inputs,
158 const size_t num_inputs, Result* results,
159 const Params& p = Params()) {
160 return Measure(reinterpret_cast<Func>(&CallClosure<Closure>),
161 reinterpret_cast<const void*>(&closure), inputs, num_inputs,
162 results, p);
163}
164
165} // namespace random_internal_nanobenchmark
166} // namespace absl
167
168#endif // ABSL_RANDOM_INTERNAL_NANOBENCHMARK_H_