Clang-format the flag declarations in Benchmark.cpp
[folly.git] / folly / Benchmark.cpp
1 /*
2  * Copyright 2016 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 // @author Andrei Alexandrescu (andrei.alexandrescu@fb.com)
18
19 #include <folly/Benchmark.h>
20 #include <folly/Foreach.h>
21 #include <folly/json.h>
22 #include <folly/String.h>
23
24 #include <algorithm>
25 #include <boost/regex.hpp>
26 #include <cmath>
27 #include <iostream>
28 #include <limits>
29 #include <utility>
30 #include <vector>
31 #include <cstring>
32
33 using namespace std;
34
35 DEFINE_bool(benchmark, false, "Run benchmarks.");
36 DEFINE_bool(json, false, "Output in JSON format.");
37
38 DEFINE_string(
39     bm_regex,
40     "",
41     "Only benchmarks whose names match this regex will be run.");
42
43 DEFINE_int64(
44     bm_min_usec,
45     100,
46     "Minimum # of microseconds we'll accept for each benchmark.");
47
48 DEFINE_int64(
49     bm_min_iters,
50     1,
51     "Minimum # of iterations we'll try for each benchmark.");
52
53 DEFINE_int32(
54     bm_max_secs,
55     1,
56     "Maximum # of seconds we'll spend on each benchmark.");
57
58 namespace folly {
59
60 BenchmarkSuspender::NanosecondsSpent BenchmarkSuspender::nsSpent;
61
62 typedef function<detail::TimeIterPair(unsigned int)> BenchmarkFun;
63
64
65 vector<tuple<string, string, BenchmarkFun>>& benchmarks() {
66   static vector<tuple<string, string, BenchmarkFun>> _benchmarks;
67   return _benchmarks;
68 }
69
70 #define FB_FOLLY_GLOBAL_BENCHMARK_BASELINE fbFollyGlobalBenchmarkBaseline
71 #define FB_STRINGIZE_X2(x) FB_STRINGIZE(x)
72
73 // Add the global baseline
74 BENCHMARK(FB_FOLLY_GLOBAL_BENCHMARK_BASELINE) {
75 #ifdef _MSC_VER
76   _ReadWriteBarrier();
77 #else
78   asm volatile("");
79 #endif
80 }
81
82 int getGlobalBenchmarkBaselineIndex() {
83   const char *global = FB_STRINGIZE_X2(FB_FOLLY_GLOBAL_BENCHMARK_BASELINE);
84   auto it = std::find_if(
85     benchmarks().begin(),
86     benchmarks().end(),
87     [global](const tuple<string, string, BenchmarkFun> &v) {
88       return get<1>(v) == global;
89     }
90   );
91   CHECK(it != benchmarks().end());
92   return it - benchmarks().begin();
93 }
94
95 #undef FB_STRINGIZE_X2
96 #undef FB_FOLLY_GLOBAL_BENCHMARK_BASELINE
97
98 void detail::addBenchmarkImpl(const char* file, const char* name,
99                               BenchmarkFun fun) {
100   benchmarks().emplace_back(file, name, std::move(fun));
101 }
102
103 /**
104  * Given a point, gives density at that point as a number 0.0 < x <=
105  * 1.0. The result is 1.0 if all samples are equal to where, and
106  * decreases near 0 if all points are far away from it. The density is
107  * computed with the help of a radial basis function.
108  */
109 static double density(const double * begin, const double *const end,
110                       const double where, const double bandwidth) {
111   assert(begin < end);
112   assert(bandwidth > 0.0);
113   double sum = 0.0;
114   FOR_EACH_RANGE (i, begin, end) {
115     auto d = (*i - where) / bandwidth;
116     sum += exp(- d * d);
117   }
118   return sum / (end - begin);
119 }
120
121 /**
122  * Computes mean and variance for a bunch of data points. Note that
123  * mean is currently not being used.
124  */
125 static pair<double, double>
126 meanVariance(const double * begin, const double *const end) {
127   assert(begin < end);
128   double sum = 0.0, sum2 = 0.0;
129   FOR_EACH_RANGE (i, begin, end) {
130     sum += *i;
131     sum2 += *i * *i;
132   }
133   auto const n = end - begin;
134   return make_pair(sum / n, sqrt((sum2 - sum * sum / n) / n));
135 }
136
137 /**
138  * Computes the mode of a sample set through brute force. Assumes
139  * input is sorted.
140  */
141 static double mode(const double * begin, const double *const end) {
142   assert(begin < end);
143   // Lower bound and upper bound for result and their respective
144   // densities.
145   auto
146     result = 0.0,
147     bestDensity = 0.0;
148
149   // Get the variance so we pass it down to density()
150   auto const sigma = meanVariance(begin, end).second;
151   if (!sigma) {
152     // No variance means constant signal
153     return *begin;
154   }
155
156   FOR_EACH_RANGE (i, begin, end) {
157     assert(i == begin || *i >= i[-1]);
158     auto candidate = density(begin, end, *i, sigma * sqrt(2.0));
159     if (candidate > bestDensity) {
160       // Found a new best
161       bestDensity = candidate;
162       result = *i;
163     } else {
164       // Density is decreasing... we could break here if we definitely
165       // knew this is unimodal.
166     }
167   }
168
169   return result;
170 }
171
172 /**
173  * Given a bunch of benchmark samples, estimate the actual run time.
174  */
175 static double estimateTime(double * begin, double * end) {
176   assert(begin < end);
177
178   // Current state of the art: get the minimum. After some
179   // experimentation, it seems taking the minimum is the best.
180
181   return *min_element(begin, end);
182
183   // What follows after estimates the time as the mode of the
184   // distribution.
185
186   // Select the awesomest (i.e. most frequent) result. We do this by
187   // sorting and then computing the longest run length.
188   sort(begin, end);
189
190   // Eliminate outliers. A time much larger than the minimum time is
191   // considered an outlier.
192   while (end[-1] > 2.0 * *begin) {
193     --end;
194     if (begin == end) {
195       LOG(INFO) << *begin;
196     }
197     assert(begin < end);
198   }
199
200   double result = 0;
201
202   /* Code used just for comparison purposes */ {
203     unsigned bestFrequency = 0;
204     unsigned candidateFrequency = 1;
205     double candidateValue = *begin;
206     for (auto current = begin + 1; ; ++current) {
207       if (current == end || *current != candidateValue) {
208         // Done with the current run, see if it was best
209         if (candidateFrequency > bestFrequency) {
210           bestFrequency = candidateFrequency;
211           result = candidateValue;
212         }
213         if (current == end) {
214           break;
215         }
216         // Start a new run
217         candidateValue = *current;
218         candidateFrequency = 1;
219       } else {
220         // Cool, inside a run, increase the frequency
221         ++candidateFrequency;
222       }
223     }
224   }
225
226   result = mode(begin, end);
227
228   return result;
229 }
230
231 static double runBenchmarkGetNSPerIteration(const BenchmarkFun& fun,
232                                             const double globalBaseline) {
233   // They key here is accuracy; too low numbers means the accuracy was
234   // coarse. We up the ante until we get to at least minNanoseconds
235   // timings.
236   static uint64_t resolutionInNs = 0;
237   if (!resolutionInNs) {
238     timespec ts;
239     CHECK_EQ(0, clock_getres(detail::DEFAULT_CLOCK_ID, &ts));
240     CHECK_EQ(0, ts.tv_sec) << "Clock sucks.";
241     CHECK_LT(0, ts.tv_nsec) << "Clock too fast for its own good.";
242     CHECK_EQ(1, ts.tv_nsec) << "Clock too coarse, upgrade your kernel.";
243     resolutionInNs = ts.tv_nsec;
244   }
245   // We choose a minimum minimum (sic) of 100,000 nanoseconds, but if
246   // the clock resolution is worse than that, it will be larger. In
247   // essence we're aiming at making the quantization noise 0.01%.
248   static const auto minNanoseconds =
249     max<uint64_t>(FLAGS_bm_min_usec * 1000UL,
250         min<uint64_t>(resolutionInNs * 100000, 1000000000ULL));
251
252   // We do measurements in several epochs and take the minimum, to
253   // account for jitter.
254   static const unsigned int epochs = 1000;
255   // We establish a total time budget as we don't want a measurement
256   // to take too long. This will curtail the number of actual epochs.
257   const uint64_t timeBudgetInNs = FLAGS_bm_max_secs * 1000000000ULL;
258   timespec global;
259   CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &global));
260
261   double epochResults[epochs] = { 0 };
262   size_t actualEpochs = 0;
263
264   for (; actualEpochs < epochs; ++actualEpochs) {
265     for (unsigned int n = FLAGS_bm_min_iters; n < (1UL << 30); n *= 2) {
266       auto const nsecsAndIter = fun(n);
267       if (nsecsAndIter.first < minNanoseconds) {
268         continue;
269       }
270       // We got an accurate enough timing, done. But only save if
271       // smaller than the current result.
272       epochResults[actualEpochs] = max(0.0, double(nsecsAndIter.first) /
273                                        nsecsAndIter.second - globalBaseline);
274       // Done with the current epoch, we got a meaningful timing.
275       break;
276     }
277     timespec now;
278     CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &now));
279     if (detail::timespecDiff(now, global) >= timeBudgetInNs) {
280       // No more time budget available.
281       ++actualEpochs;
282       break;
283     }
284   }
285
286   // If the benchmark was basically drowned in baseline noise, it's
287   // possible it became negative.
288   return max(0.0, estimateTime(epochResults, epochResults + actualEpochs));
289 }
290
291 struct ScaleInfo {
292   double boundary;
293   const char* suffix;
294 };
295
296 static const ScaleInfo kTimeSuffixes[] {
297   { 365.25 * 24 * 3600, "years" },
298   { 24 * 3600, "days" },
299   { 3600, "hr" },
300   { 60, "min" },
301   { 1, "s" },
302   { 1E-3, "ms" },
303   { 1E-6, "us" },
304   { 1E-9, "ns" },
305   { 1E-12, "ps" },
306   { 1E-15, "fs" },
307   { 0, nullptr },
308 };
309
310 static const ScaleInfo kMetricSuffixes[] {
311   { 1E24, "Y" },  // yotta
312   { 1E21, "Z" },  // zetta
313   { 1E18, "X" },  // "exa" written with suffix 'X' so as to not create
314                   //   confusion with scientific notation
315   { 1E15, "P" },  // peta
316   { 1E12, "T" },  // terra
317   { 1E9, "G" },   // giga
318   { 1E6, "M" },   // mega
319   { 1E3, "K" },   // kilo
320   { 1, "" },
321   { 1E-3, "m" },  // milli
322   { 1E-6, "u" },  // micro
323   { 1E-9, "n" },  // nano
324   { 1E-12, "p" }, // pico
325   { 1E-15, "f" }, // femto
326   { 1E-18, "a" }, // atto
327   { 1E-21, "z" }, // zepto
328   { 1E-24, "y" }, // yocto
329   { 0, nullptr },
330 };
331
332 static string humanReadable(double n, unsigned int decimals,
333                             const ScaleInfo* scales) {
334   if (std::isinf(n) || std::isnan(n)) {
335     return folly::to<string>(n);
336   }
337
338   const double absValue = fabs(n);
339   const ScaleInfo* scale = scales;
340   while (absValue < scale[0].boundary && scale[1].suffix != nullptr) {
341     ++scale;
342   }
343
344   const double scaledValue = n / scale->boundary;
345   return stringPrintf("%.*f%s", decimals, scaledValue, scale->suffix);
346 }
347
348 static string readableTime(double n, unsigned int decimals) {
349   return humanReadable(n, decimals, kTimeSuffixes);
350 }
351
352 static string metricReadable(double n, unsigned int decimals) {
353   return humanReadable(n, decimals, kMetricSuffixes);
354 }
355
356 static void printBenchmarkResultsAsTable(
357   const vector<tuple<string, string, double> >& data) {
358   // Width available
359   static const unsigned int columns = 76;
360
361   // Compute the longest benchmark name
362   size_t longestName = 0;
363   FOR_EACH_RANGE (i, 1, benchmarks().size()) {
364     longestName = max(longestName, get<1>(benchmarks()[i]).size());
365   }
366
367   // Print a horizontal rule
368   auto separator = [&](char pad) {
369     puts(string(columns, pad).c_str());
370   };
371
372   // Print header for a file
373   auto header = [&](const string& file) {
374     separator('=');
375     printf("%-*srelative  time/iter  iters/s\n",
376            columns - 28, file.c_str());
377     separator('=');
378   };
379
380   double baselineNsPerIter = numeric_limits<double>::max();
381   string lastFile;
382
383   for (auto& datum : data) {
384     auto file = get<0>(datum);
385     if (file != lastFile) {
386       // New file starting
387       header(file);
388       lastFile = file;
389     }
390
391     string s = get<1>(datum);
392     if (s == "-") {
393       separator('-');
394       continue;
395     }
396     bool useBaseline /* = void */;
397     if (s[0] == '%') {
398       s.erase(0, 1);
399       useBaseline = true;
400     } else {
401       baselineNsPerIter = get<2>(datum);
402       useBaseline = false;
403     }
404     s.resize(columns - 29, ' ');
405     auto nsPerIter = get<2>(datum);
406     auto secPerIter = nsPerIter / 1E9;
407     auto itersPerSec = (secPerIter == 0)
408                            ? std::numeric_limits<double>::infinity()
409                            : (1 / secPerIter);
410     if (!useBaseline) {
411       // Print without baseline
412       printf("%*s           %9s  %7s\n",
413              static_cast<int>(s.size()), s.c_str(),
414              readableTime(secPerIter, 2).c_str(),
415              metricReadable(itersPerSec, 2).c_str());
416     } else {
417       // Print with baseline
418       auto rel = baselineNsPerIter / nsPerIter * 100.0;
419       printf("%*s %7.2f%%  %9s  %7s\n",
420              static_cast<int>(s.size()), s.c_str(),
421              rel,
422              readableTime(secPerIter, 2).c_str(),
423              metricReadable(itersPerSec, 2).c_str());
424     }
425   }
426   separator('=');
427 }
428
429 static void printBenchmarkResultsAsJson(
430   const vector<tuple<string, string, double> >& data) {
431   dynamic d = dynamic::object;
432   for (auto& datum: data) {
433     d[std::get<1>(datum)] = std::get<2>(datum) * 1000.;
434   }
435
436   printf("%s\n", toPrettyJson(d).c_str());
437 }
438
439 static void printBenchmarkResults(
440   const vector<tuple<string, string, double> >& data) {
441
442   if (FLAGS_json) {
443     printBenchmarkResultsAsJson(data);
444   } else {
445     printBenchmarkResultsAsTable(data);
446   }
447 }
448
449 void runBenchmarks() {
450   CHECK(!benchmarks().empty());
451
452   vector<tuple<string, string, double>> results;
453   results.reserve(benchmarks().size() - 1);
454
455   std::unique_ptr<boost::regex> bmRegex;
456   if (!FLAGS_bm_regex.empty()) {
457     bmRegex.reset(new boost::regex(FLAGS_bm_regex));
458   }
459
460   // PLEASE KEEP QUIET. MEASUREMENTS IN PROGRESS.
461
462   unsigned int baselineIndex = getGlobalBenchmarkBaselineIndex();
463
464   auto const globalBaseline =
465       runBenchmarkGetNSPerIteration(get<2>(benchmarks()[baselineIndex]), 0);
466   FOR_EACH_RANGE (i, 0, benchmarks().size()) {
467     if (i == baselineIndex) {
468       continue;
469     }
470     double elapsed = 0.0;
471     if (get<1>(benchmarks()[i]) != "-") { // skip separators
472       if (bmRegex && !boost::regex_search(get<1>(benchmarks()[i]), *bmRegex)) {
473         continue;
474       }
475       elapsed = runBenchmarkGetNSPerIteration(get<2>(benchmarks()[i]),
476                                               globalBaseline);
477     }
478     results.emplace_back(get<0>(benchmarks()[i]),
479                          get<1>(benchmarks()[i]), elapsed);
480   }
481
482   // PLEASE MAKE NOISE. MEASUREMENTS DONE.
483
484   printBenchmarkResults(results);
485 }
486
487 } // namespace folly