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