add support for whenAll to waitWithSemaphore
[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<uint64_t(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 nsecs = fun(n);
235       if (nsecs < 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(nsecs) / n - globalBaseline);
241       // Done with the current epoch, we got a meaningful timing.
242       break;
243     }
244     timespec now;
245     CHECK_EQ(0, clock_gettime(CLOCK_REALTIME, &now));
246     if (detail::timespecDiff(now, global) >= timeBudgetInNs) {
247       // No more time budget available.
248       ++actualEpochs;
249       break;
250     }
251   }
252
253   // If the benchmark was basically drowned in baseline noise, it's
254   // possible it became negative.
255   return max(0.0, estimateTime(epochResults, epochResults + actualEpochs));
256 }
257
258 struct ScaleInfo {
259   double boundary;
260   const char* suffix;
261 };
262
263 static const ScaleInfo kTimeSuffixes[] {
264   { 365.25 * 24 * 3600, "years" },
265   { 24 * 3600, "days" },
266   { 3600, "hr" },
267   { 60, "min" },
268   { 1, "s" },
269   { 1E-3, "ms" },
270   { 1E-6, "us" },
271   { 1E-9, "ns" },
272   { 1E-12, "ps" },
273   { 1E-15, "fs" },
274   { 0, nullptr },
275 };
276
277 static const ScaleInfo kMetricSuffixes[] {
278   { 1E24, "Y" },  // yotta
279   { 1E21, "Z" },  // zetta
280   { 1E18, "X" },  // "exa" written with suffix 'X' so as to not create
281                   //   confusion with scientific notation
282   { 1E15, "P" },  // peta
283   { 1E12, "T" },  // terra
284   { 1E9, "G" },   // giga
285   { 1E6, "M" },   // mega
286   { 1E3, "K" },   // kilo
287   { 1, "" },
288   { 1E-3, "m" },  // milli
289   { 1E-6, "u" },  // micro
290   { 1E-9, "n" },  // nano
291   { 1E-12, "p" }, // pico
292   { 1E-15, "f" }, // femto
293   { 1E-18, "a" }, // atto
294   { 1E-21, "z" }, // zepto
295   { 1E-24, "y" }, // yocto
296   { 0, nullptr },
297 };
298
299 static string humanReadable(double n, unsigned int decimals,
300                             const ScaleInfo* scales) {
301   if (std::isinf(n) || std::isnan(n)) {
302     return folly::to<string>(n);
303   }
304
305   const double absValue = fabs(n);
306   const ScaleInfo* scale = scales;
307   while (absValue < scale[0].boundary && scale[1].suffix != nullptr) {
308     ++scale;
309   }
310
311   const double scaledValue = n / scale->boundary;
312   return stringPrintf("%.*f%s", decimals, scaledValue, scale->suffix);
313 }
314
315 static string readableTime(double n, unsigned int decimals) {
316   return humanReadable(n, decimals, kTimeSuffixes);
317 }
318
319 static string metricReadable(double n, unsigned int decimals) {
320   return humanReadable(n, decimals, kMetricSuffixes);
321 }
322
323 static void printBenchmarkResultsAsTable(
324   const vector<tuple<const char*, const char*, double> >& data) {
325   // Width available
326   static const unsigned int columns = 76;
327
328   // Compute the longest benchmark name
329   size_t longestName = 0;
330   FOR_EACH_RANGE (i, 1, benchmarks.size()) {
331     longestName = max(longestName, strlen(get<1>(benchmarks[i])));
332   }
333
334   // Print a horizontal rule
335   auto separator = [&](char pad) {
336     puts(string(columns, pad).c_str());
337   };
338
339   // Print header for a file
340   auto header = [&](const char* file) {
341     separator('=');
342     printf("%-*srelative  time/iter  iters/s\n",
343            columns - 28, file);
344     separator('=');
345   };
346
347   double baselineNsPerIter = numeric_limits<double>::max();
348   const char* lastFile = "";
349
350   for (auto& datum : data) {
351     auto file = get<0>(datum);
352     if (strcmp(file, lastFile)) {
353       // New file starting
354       header(file);
355       lastFile = file;
356     }
357
358     string s = get<1>(datum);
359     if (s == "-") {
360       separator('-');
361       continue;
362     }
363     bool useBaseline /* = void */;
364     if (s[0] == '%') {
365       s.erase(0, 1);
366       useBaseline = true;
367     } else {
368       baselineNsPerIter = get<2>(datum);
369       useBaseline = false;
370     }
371     s.resize(columns - 29, ' ');
372     auto nsPerIter = get<2>(datum);
373     auto secPerIter = nsPerIter / 1E9;
374     auto itersPerSec = 1 / secPerIter;
375     if (!useBaseline) {
376       // Print without baseline
377       printf("%*s           %9s  %7s\n",
378              static_cast<int>(s.size()), s.c_str(),
379              readableTime(secPerIter, 2).c_str(),
380              metricReadable(itersPerSec, 2).c_str());
381     } else {
382       // Print with baseline
383       auto rel = baselineNsPerIter / nsPerIter * 100.0;
384       printf("%*s %7.2f%%  %9s  %7s\n",
385              static_cast<int>(s.size()), s.c_str(),
386              rel,
387              readableTime(secPerIter, 2).c_str(),
388              metricReadable(itersPerSec, 2).c_str());
389     }
390   }
391   separator('=');
392 }
393
394 static void printBenchmarkResultsAsJson(
395   const vector<tuple<const char*, const char*, double> >& data) {
396   dynamic d = dynamic::object;
397   for (auto& datum: data) {
398     d[std::get<1>(datum)] = std::get<2>(datum) * 1000.;
399   }
400
401   printf("%s\n", toPrettyJson(d).c_str());
402 }
403
404 static void printBenchmarkResults(
405   const vector<tuple<const char*, const char*, double> >& data) {
406
407   if (FLAGS_json) {
408     printBenchmarkResultsAsJson(data);
409   } else {
410     printBenchmarkResultsAsTable(data);
411   }
412 }
413
414 void runBenchmarks() {
415   CHECK(!benchmarks.empty());
416
417   vector<tuple<const char*, const char*, double>> results;
418   results.reserve(benchmarks.size() - 1);
419
420   std::unique_ptr<boost::regex> bmRegex;
421   if (!FLAGS_bm_regex.empty()) {
422     bmRegex.reset(new boost::regex(FLAGS_bm_regex));
423   }
424
425   // PLEASE KEEP QUIET. MEASUREMENTS IN PROGRESS.
426
427   auto const globalBaseline = runBenchmarkGetNSPerIteration(
428     get<2>(benchmarks.front()), 0);
429   FOR_EACH_RANGE (i, 1, benchmarks.size()) {
430     double elapsed = 0.0;
431     if (strcmp(get<1>(benchmarks[i]), "-") != 0) { // skip separators
432       if (bmRegex && !boost::regex_search(get<1>(benchmarks[i]), *bmRegex)) {
433         continue;
434       }
435       elapsed = runBenchmarkGetNSPerIteration(get<2>(benchmarks[i]),
436                                               globalBaseline);
437     }
438     results.emplace_back(get<0>(benchmarks[i]),
439                          get<1>(benchmarks[i]), elapsed);
440   }
441
442   // PLEASE MAKE NOISE. MEASUREMENTS DONE.
443
444   printBenchmarkResults(results);
445 }
446
447 } // namespace folly