1 //===-- Benchmark function --------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 // This file mainly defines a `Benchmark` function. 10 // 11 // The benchmarking process is as follows: 12 // - We start by measuring the time it takes to run the function 13 // `InitialIterations` times. This is called a Sample. From this we can derive 14 // the time it took to run a single iteration. 15 // 16 // - We repeat the previous step with a greater number of iterations to lower 17 // the impact of the measurement. We can derive a more precise estimation of the 18 // runtime for a single iteration. 19 // 20 // - Each sample gives a more accurate estimation of the runtime for a single 21 // iteration but also takes more time to run. We stop the process when: 22 // * The measure stabilize under a certain precision (Epsilon), 23 // * The overall benchmarking time is greater than MaxDuration, 24 // * The overall sample count is greater than MaxSamples, 25 // * The last sample used more than MaxIterations iterations. 26 // 27 // - We also makes sure that the benchmark doesn't run for a too short period of 28 // time by defining MinDuration and MinSamples. 29 30 #ifndef LLVM_LIBC_UTILS_BENCHMARK_BENCHMARK_H 31 #define LLVM_LIBC_UTILS_BENCHMARK_BENCHMARK_H 32 33 #include "benchmark/benchmark.h" 34 #include "llvm/ADT/ArrayRef.h" 35 #include "llvm/ADT/Optional.h" 36 #include "llvm/ADT/SmallVector.h" 37 #include <array> 38 #include <chrono> 39 #include <cstdint> 40 41 namespace llvm { 42 namespace libc_benchmarks { 43 44 // Makes sure the binary was compiled in release mode and that frequency 45 // governor is set on performance. 46 void checkRequirements(); 47 48 using Duration = std::chrono::duration<double>; 49 50 enum class BenchmarkLog { 51 None, // Don't keep the internal state of the benchmark. 52 Last, // Keep only the last batch. 53 Full // Keep all iterations states, useful for testing or debugging. 54 }; 55 56 // An object to configure the benchmark stopping conditions. 57 // See documentation at the beginning of the file for the overall algorithm and 58 // meaning of each field. 59 struct BenchmarkOptions { 60 // The minimum time for which the benchmark is running. 61 Duration MinDuration = std::chrono::seconds(0); 62 // The maximum time for which the benchmark is running. 63 Duration MaxDuration = std::chrono::seconds(10); 64 // The number of iterations in the first sample. 65 uint32_t InitialIterations = 1; 66 // The maximum number of iterations for any given sample. 67 uint32_t MaxIterations = 10000000; 68 // The minimum number of samples. 69 uint32_t MinSamples = 4; 70 // The maximum number of samples. 71 uint32_t MaxSamples = 1000; 72 // The benchmark will stop is the relative difference between the current and 73 // the last estimation is less than epsilon. This is 1% by default. 74 double Epsilon = 0.01; 75 // The number of iterations grows exponentially between each sample. 76 // Must be greater or equal to 1. 77 double ScalingFactor = 1.4; 78 BenchmarkLog Log = BenchmarkLog::None; 79 }; 80 81 // The state of a benchmark. 82 enum class BenchmarkStatus { 83 Running, 84 MaxDurationReached, 85 MaxIterationsReached, 86 MaxSamplesReached, 87 PrecisionReached, 88 }; 89 90 // The internal state of the benchmark, useful to debug, test or report 91 // statistics. 92 struct BenchmarkState { 93 size_t LastSampleIterations; 94 Duration LastBatchElapsed; 95 BenchmarkStatus CurrentStatus; 96 Duration CurrentBestGuess; // The time estimation for a single run of `foo`. 97 double ChangeRatio; // The change in time estimation between previous and 98 // current samples. 99 }; 100 101 // A lightweight result for a benchmark. 102 struct BenchmarkResult { 103 BenchmarkStatus TerminationStatus = BenchmarkStatus::Running; 104 Duration BestGuess = {}; 105 llvm::Optional<llvm::SmallVector<BenchmarkState, 16>> MaybeBenchmarkLog; 106 }; 107 108 // Stores information about a cache in the host memory system. 109 struct CacheInfo { 110 std::string Type; // e.g. "Instruction", "Data", "Unified". 111 int Level; // 0 is closest to processing unit. 112 int Size; // In bytes. 113 int NumSharing; // The number of processing units (Hyper-Threading Thread) 114 // with which this cache is shared. 115 }; 116 117 // Stores information about the host. 118 struct HostState { 119 std::string CpuName; // returns a string compatible with the -march option. 120 double CpuFrequency; // in Hertz. 121 std::vector<CacheInfo> Caches; 122 123 static HostState get(); 124 }; 125 126 namespace internal { 127 128 struct Measurement { 129 size_t Iterations = 0; 130 Duration Elapsed = {}; 131 }; 132 133 // Updates the estimation of the elapsed time for a single iteration. 134 class RefinableRuntimeEstimation { 135 Duration TotalTime = {}; 136 size_t TotalIterations = 0; 137 138 public: 139 Duration update(const Measurement &M) { 140 assert(M.Iterations > 0); 141 // Duration is encoded as a double (see definition). 142 // `TotalTime` and `M.Elapsed` are of the same magnitude so we don't expect 143 // loss of precision due to radically different scales. 144 TotalTime += M.Elapsed; 145 TotalIterations += M.Iterations; 146 return TotalTime / TotalIterations; 147 } 148 }; 149 150 // This class tracks the progression of the runtime estimation. 151 class RuntimeEstimationProgression { 152 RefinableRuntimeEstimation RRE; 153 154 public: 155 Duration CurrentEstimation = {}; 156 157 // Returns the change ratio between our best guess so far and the one from the 158 // new measurement. 159 double computeImprovement(const Measurement &M) { 160 const Duration NewEstimation = RRE.update(M); 161 const double Ratio = fabs(((CurrentEstimation / NewEstimation) - 1.0)); 162 CurrentEstimation = NewEstimation; 163 return Ratio; 164 } 165 }; 166 167 } // namespace internal 168 169 // Measures the runtime of `foo` until conditions defined by `Options` are met. 170 // 171 // To avoid measurement's imprecisions we measure batches of `foo`. 172 // The batch size is growing by `ScalingFactor` to minimize the effect of 173 // measuring. 174 // 175 // Note: The benchmark is not responsible for serializing the executions of 176 // `foo`. It is not suitable for measuring, very small & side effect free 177 // functions, as the processor is free to execute several executions in 178 // parallel. 179 // 180 // - Options: A set of parameters controlling the stopping conditions for the 181 // benchmark. 182 // - foo: The function under test. It takes one value and returns one value. 183 // The input value is used to randomize the execution of `foo` as part of a 184 // batch to mitigate the effect of the branch predictor. Signature: 185 // `ProductType foo(ParameterProvider::value_type value);` 186 // The output value is a product of the execution of `foo` and prevents the 187 // compiler from optimizing out foo's body. 188 // - ParameterProvider: An object responsible for providing a range of 189 // `Iterations` values to use as input for `foo`. The `value_type` of the 190 // returned container has to be compatible with `foo` argument. 191 // Must implement one of: 192 // `Container<ParameterType> generateBatch(size_t Iterations);` 193 // `const Container<ParameterType>& generateBatch(size_t Iterations);` 194 // - Clock: An object providing the current time. Must implement: 195 // `std::chrono::time_point now();` 196 template <typename Function, typename ParameterProvider, 197 typename BenchmarkClock = const std::chrono::high_resolution_clock> 198 BenchmarkResult benchmark(const BenchmarkOptions &Options, 199 ParameterProvider &PP, Function foo, 200 BenchmarkClock &Clock = BenchmarkClock()) { 201 BenchmarkResult Result; 202 internal::RuntimeEstimationProgression REP; 203 Duration TotalBenchmarkDuration = {}; 204 size_t Iterations = std::max(Options.InitialIterations, uint32_t(1)); 205 size_t Samples = 0; 206 if (Options.ScalingFactor < 1.0) 207 report_fatal_error("ScalingFactor should be >= 1"); 208 if (Options.Log != BenchmarkLog::None) 209 Result.MaybeBenchmarkLog.emplace(); 210 for (;;) { 211 // Request a new Batch of size `Iterations`. 212 const auto &Batch = PP.generateBatch(Iterations); 213 214 // Measuring this Batch. 215 const auto StartTime = Clock.now(); 216 for (const auto Parameter : Batch) { 217 const auto Production = foo(Parameter); 218 benchmark::DoNotOptimize(Production); 219 } 220 const auto EndTime = Clock.now(); 221 const Duration Elapsed = EndTime - StartTime; 222 223 // Updating statistics. 224 ++Samples; 225 TotalBenchmarkDuration += Elapsed; 226 const double ChangeRatio = REP.computeImprovement({Iterations, Elapsed}); 227 Result.BestGuess = REP.CurrentEstimation; 228 229 // Stopping condition. 230 if (TotalBenchmarkDuration >= Options.MinDuration && 231 Samples >= Options.MinSamples && ChangeRatio < Options.Epsilon) 232 Result.TerminationStatus = BenchmarkStatus::PrecisionReached; 233 else if (Samples >= Options.MaxSamples) 234 Result.TerminationStatus = BenchmarkStatus::MaxSamplesReached; 235 else if (TotalBenchmarkDuration >= Options.MaxDuration) 236 Result.TerminationStatus = BenchmarkStatus::MaxDurationReached; 237 else if (Iterations >= Options.MaxIterations) 238 Result.TerminationStatus = BenchmarkStatus::MaxIterationsReached; 239 240 if (Result.MaybeBenchmarkLog) { 241 auto &BenchmarkLog = *Result.MaybeBenchmarkLog; 242 if (Options.Log == BenchmarkLog::Last && !BenchmarkLog.empty()) 243 BenchmarkLog.pop_back(); 244 BenchmarkState BS; 245 BS.LastSampleIterations = Iterations; 246 BS.LastBatchElapsed = Elapsed; 247 BS.CurrentStatus = Result.TerminationStatus; 248 BS.CurrentBestGuess = Result.BestGuess; 249 BS.ChangeRatio = ChangeRatio; 250 BenchmarkLog.push_back(BS); 251 } 252 253 if (Result.TerminationStatus != BenchmarkStatus::Running) 254 return Result; 255 256 if (Options.ScalingFactor > 1 && 257 Iterations * Options.ScalingFactor == Iterations) 258 report_fatal_error( 259 "`Iterations *= ScalingFactor` is idempotent, increase ScalingFactor " 260 "or InitialIterations."); 261 262 Iterations *= Options.ScalingFactor; 263 } 264 } 265 266 // Interprets `Array` as a circular buffer of `Size` elements. 267 template <typename T> class CircularArrayRef { 268 llvm::ArrayRef<T> Array; 269 size_t Size; 270 271 public: 272 using value_type = T; 273 using reference = T &; 274 using const_reference = const T &; 275 using difference_type = ssize_t; 276 using size_type = size_t; 277 278 class const_iterator 279 : public std::iterator<std::input_iterator_tag, T, ssize_t> { 280 llvm::ArrayRef<T> Array; 281 size_t Index; 282 283 public: 284 explicit const_iterator(llvm::ArrayRef<T> Array, size_t Index = 0) 285 : Array(Array), Index(Index) {} 286 const_iterator &operator++() { 287 ++Index; 288 return *this; 289 } 290 bool operator==(const_iterator Other) const { return Index == Other.Index; } 291 bool operator!=(const_iterator Other) const { return !(*this == Other); } 292 const T &operator*() const { return Array[Index % Array.size()]; } 293 }; 294 295 CircularArrayRef(llvm::ArrayRef<T> Array, size_t Size) 296 : Array(Array), Size(Size) { 297 assert(Array.size() > 0); 298 } 299 300 const_iterator begin() const { return const_iterator(Array); } 301 const_iterator end() const { return const_iterator(Array, Size); } 302 }; 303 304 // A convenient helper to produce a CircularArrayRef from an ArrayRef. 305 template <typename T> 306 CircularArrayRef<T> cycle(llvm::ArrayRef<T> Array, size_t Size) { 307 return {Array, Size}; 308 } 309 310 // Creates an std::array which storage size is constrained under `Bytes`. 311 template <typename T, size_t Bytes> 312 using ByteConstrainedArray = std::array<T, Bytes / sizeof(T)>; 313 314 // A convenient helper to produce a CircularArrayRef from a 315 // ByteConstrainedArray. 316 template <typename T, size_t N> 317 CircularArrayRef<T> cycle(const std::array<T, N> &Container, size_t Size) { 318 return {llvm::ArrayRef<T>(Container.cbegin(), Container.cend()), Size}; 319 } 320 321 } // namespace libc_benchmarks 322 } // namespace llvm 323 324 #endif // LLVM_LIBC_UTILS_BENCHMARK_BENCHMARK_H 325