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