1 // Copyright (c) 2015-2018 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #ifndef BITCOIN_BENCH_BENCH_H 6 #define BITCOIN_BENCH_BENCH_H 7 8 #include <functional> 9 #include <limits> 10 #include <map> 11 #include <string> 12 #include <vector> 13 #include <chrono> 14 15 #include <boost/preprocessor/cat.hpp> 16 #include <boost/preprocessor/stringize.hpp> 17 18 // Simple micro-benchmarking framework; API mostly matches a subset of the Google Benchmark 19 // framework (see https://github.com/google/benchmark) 20 // Why not use the Google Benchmark framework? Because adding Yet Another Dependency 21 // (that uses cmake as its build system and has lots of features we don't need) isn't 22 // worth it. 23 24 /* 25 * Usage: 26 27 static void CODE_TO_TIME(benchmark::State& state) 28 { 29 ... do any setup needed... 30 while (state.KeepRunning()) { 31 ... do stuff you want to time... 32 } 33 ... do any cleanup needed... 34 } 35 36 // default to running benchmark for 5000 iterations 37 BENCHMARK(CODE_TO_TIME, 5000); 38 39 */ 40 41 namespace benchmark { 42 // In case high_resolution_clock is steady, prefer that, otherwise use steady_clock. 43 struct best_clock { 44 using hi_res_clock = std::chrono::high_resolution_clock; 45 using steady_clock = std::chrono::steady_clock; 46 using type = std::conditional<hi_res_clock::is_steady, hi_res_clock, steady_clock>::type; 47 }; 48 using clock = best_clock::type; 49 using time_point = clock::time_point; 50 using duration = clock::duration; 51 52 class Printer; 53 54 class State 55 { 56 public: 57 std::string m_name; 58 uint64_t m_num_iters_left; 59 const uint64_t m_num_iters; 60 const uint64_t m_num_evals; 61 std::vector<double> m_elapsed_results; 62 time_point m_start_time; 63 64 bool UpdateTimer(time_point finish_time); 65 State(std::string name,uint64_t num_evals,double num_iters,Printer & printer)66 State(std::string name, uint64_t num_evals, double num_iters, Printer& printer) : m_name(name), m_num_iters_left(0), m_num_iters(num_iters), m_num_evals(num_evals) 67 { 68 } 69 KeepRunning()70 inline bool KeepRunning() 71 { 72 if (m_num_iters_left--) { 73 return true; 74 } 75 76 bool result = UpdateTimer(clock::now()); 77 // measure again so runtime of UpdateTimer is not included 78 m_start_time = clock::now(); 79 return result; 80 } 81 }; 82 83 typedef std::function<void(State&)> BenchFunction; 84 85 class BenchRunner 86 { 87 struct Bench { 88 BenchFunction func; 89 uint64_t num_iters_for_one_second; 90 }; 91 typedef std::map<std::string, Bench> BenchmarkMap; 92 static BenchmarkMap& benchmarks(); 93 94 public: 95 BenchRunner(std::string name, BenchFunction func, uint64_t num_iters_for_one_second); 96 97 static void RunAll(Printer& printer, uint64_t num_evals, double scaling, const std::string& filter, bool is_list_only); 98 }; 99 100 // interface to output benchmark results. 101 class Printer 102 { 103 public: ~Printer()104 virtual ~Printer() {} 105 virtual void header() = 0; 106 virtual void result(const State& state) = 0; 107 virtual void footer() = 0; 108 }; 109 110 // default printer to console, shows min, max, median. 111 class ConsolePrinter : public Printer 112 { 113 public: 114 void header() override; 115 void result(const State& state) override; 116 void footer() override; 117 }; 118 119 // creates box plot with plotly.js 120 class PlotlyPrinter : public Printer 121 { 122 public: 123 PlotlyPrinter(std::string plotly_url, int64_t width, int64_t height); 124 void header() override; 125 void result(const State& state) override; 126 void footer() override; 127 128 private: 129 std::string m_plotly_url; 130 int64_t m_width; 131 int64_t m_height; 132 }; 133 } 134 135 136 // BENCHMARK(foo, num_iters_for_one_second) expands to: benchmark::BenchRunner bench_11foo("foo", num_iterations); 137 // Choose a num_iters_for_one_second that takes roughly 1 second. The goal is that all benchmarks should take approximately 138 // the same time, and scaling factor can be used that the total time is appropriate for your system. 139 #define BENCHMARK(n, num_iters_for_one_second) \ 140 benchmark::BenchRunner BOOST_PP_CAT(bench_, BOOST_PP_CAT(__LINE__, n))(BOOST_PP_STRINGIZE(n), n, (num_iters_for_one_second)); 141 142 #endif // BITCOIN_BENCH_BENCH_H 143