Monero
performance_tests.h
Go to the documentation of this file.
1 // Copyright (c) 2014-2022, The Monero Project
2 //
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without modification, are
6 // permitted provided that the following conditions are met:
7 //
8 // 1. Redistributions of source code must retain the above copyright notice, this list of
9 // conditions and the following disclaimer.
10 //
11 // 2. Redistributions in binary form must reproduce the above copyright notice, this list
12 // of conditions and the following disclaimer in the documentation and/or other
13 // materials provided with the distribution.
14 //
15 // 3. Neither the name of the copyright holder nor the names of its contributors may be
16 // used to endorse or promote products derived from this software without specific
17 // prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
30 
31 #pragma once
32 
33 #include <iostream>
34 #include <stdint.h>
35 
36 #include <boost/chrono.hpp>
37 #include <boost/regex.hpp>
38 
39 #include "misc_language.h"
40 #include "stats.h"
41 #include "common/perf_timer.h"
42 #include "common/timings.h"
43 
45 {
46 public:
47  typedef boost::chrono::high_resolution_clock clock;
48 
50  {
51  m_base = clock::now();
52  }
53 
54  void start()
55  {
56  m_start = clock::now();
57  }
58 
59  int elapsed_ms()
60  {
61  clock::duration elapsed = clock::now() - m_start;
62  return static_cast<int>(boost::chrono::duration_cast<boost::chrono::milliseconds>(elapsed).count());
63  }
64 
65 private:
66  clock::time_point m_base;
67  clock::time_point m_start;
68 };
69 
70 struct Params
71 {
73  bool verbose;
74  bool stats;
75  unsigned loop_multiplier;
76 };
77 
78 template <typename T>
80 {
81 public:
82  test_runner(const Params &params)
83  : m_elapsed(0)
84  , m_params(params)
85  , m_per_call_timers(T::loop_count * params.loop_multiplier, {true})
86  {
87  }
88 
89  bool run()
90  {
91  static_assert(0 < T::loop_count, "T::loop_count must be greater than 0");
92 
93  T test;
94  if (!test.init())
95  return false;
96 
97  performance_timer timer;
98  timer.start();
99  warm_up();
100  if (m_params.verbose)
101  std::cout << "Warm up: " << timer.elapsed_ms() << " ms" << std::endl;
102 
103  timer.start();
104  for (size_t i = 0; i < T::loop_count * m_params.loop_multiplier; ++i)
105  {
106  if (m_params.stats)
107  m_per_call_timers[i].resume();
108  if (!test.test())
109  return false;
110  if (m_params.stats)
111  m_per_call_timers[i].pause();
112  }
113  m_elapsed = timer.elapsed_ms();
115 
116  return true;
117  }
118 
119  int elapsed_time() const { return m_elapsed; }
120  size_t get_size() const { return m_stats->get_size(); }
121 
122  int time_per_call(int scale = 1) const
123  {
124  static_assert(0 < T::loop_count, "T::loop_count must be greater than 0");
125  return m_elapsed * scale / (T::loop_count * m_params.loop_multiplier);
126  }
127 
128  uint64_t get_min() const { return m_stats->get_min(); }
129  uint64_t get_max() const { return m_stats->get_max(); }
130  double get_mean() const { return m_stats->get_mean(); }
131  uint64_t get_median() const { return m_stats->get_median(); }
132  double get_stddev() const { return m_stats->get_standard_deviation(); }
133  double get_non_parametric_skew() const { return m_stats->get_non_parametric_skew(); }
134  std::vector<uint64_t> get_quantiles(size_t n) const { return m_stats->get_quantiles(n); }
135 
136  bool is_same_distribution(size_t npoints, double mean, double stddev) const
137  {
138  return m_stats->is_same_distribution_99(npoints, mean, stddev);
139  }
140 
141 private:
146  {
147  const size_t warm_up_rounds = 1000 * 1000 * 1000;
148  m_warm_up = 0;
149  for (size_t i = 0; i < warm_up_rounds; ++i)
150  {
151  ++m_warm_up;
152  }
153  return m_warm_up;
154  }
155 
156 private:
157  volatile uint64_t m_warm_up;
160  std::vector<tools::PerformanceTimer> m_per_call_timers;
161  std::unique_ptr<Stats<tools::PerformanceTimer, uint64_t>> m_stats;
162 };
163 
164 template <typename T>
165 void run_test(const std::string &filter, Params &params, const char* test_name)
166 {
167  boost::smatch match;
168  if (!filter.empty() && !boost::regex_match(std::string(test_name), match, boost::regex(filter)))
169  return;
170 
171  test_runner<T> runner(params);
172  if (runner.run())
173  {
174  if (params.verbose)
175  {
176  std::cout << test_name << " - OK:\n";
177  std::cout << " loop count: " << T::loop_count * params.loop_multiplier << '\n';
178  std::cout << " elapsed: " << runner.elapsed_time() << " ms\n";
179  if (params.stats)
180  {
181  std::cout << " min: " << runner.get_min() << " ns\n";
182  std::cout << " max: " << runner.get_max() << " ns\n";
183  std::cout << " median: " << runner.get_median() << " ns\n";
184  std::cout << " std dev: " << runner.get_stddev() << " ns\n";
185  }
186  }
187  else
188  {
189  std::cout << test_name << " (" << T::loop_count * params.loop_multiplier << " calls) - OK:";
190  }
191  const char *unit = "ms";
192  double scale = 1000000;
193  uint64_t time_per_call = runner.time_per_call();
194  if (time_per_call < 100) {
195  scale = 1000;
196  time_per_call = runner.time_per_call(1000);
197 #ifdef _WIN32
198  unit = "us";
199 #else
200  unit = "µs";
201 #endif
202  }
203  const auto quantiles = runner.get_quantiles(10);
204  double min = runner.get_min();
205  double max = runner.get_max();
206  double med = runner.get_median();
207  double mean = runner.get_mean();
208  double stddev = runner.get_stddev();
209  double npskew = runner.get_non_parametric_skew();
210 
211  std::vector<TimingsDatabase::instance> prev_instances = params.td.get(test_name);
212  params.td.add(test_name, {time(NULL), runner.get_size(), min, max, mean, med, stddev, npskew, quantiles});
213 
214  std::cout << (params.verbose ? " time per call: " : " ") << time_per_call << " " << unit << "/call" << (params.verbose ? "\n" : "");
215  if (params.stats)
216  {
217  uint64_t mins = min / scale;
218  uint64_t meds = med / scale;
219  uint64_t p95s = quantiles[9] / scale;
220  uint64_t stddevs = stddev / scale;
221  std::string cmp;
222  if (!prev_instances.empty())
223  {
224  const TimingsDatabase::instance &prev_instance = prev_instances.back();
225  if (!runner.is_same_distribution(prev_instance.npoints, prev_instance.mean, prev_instance.stddev))
226  {
227  double pc = fabs(100. * (prev_instance.mean - runner.get_mean()) / prev_instance.mean);
228  cmp = ", " + std::to_string(pc) + "% " + (mean > prev_instance.mean ? "slower" : "faster");
229  }
230 cmp += " -- " + std::to_string(prev_instance.mean);
231  }
232  std::cout << " (min " << mins << " " << unit << ", 90th " << p95s << " " << unit << ", median " << meds << " " << unit << ", std dev " << stddevs << " " << unit << ")" << cmp;
233  }
234  std::cout << std::endl;
235  }
236  else
237  {
238  std::cout << test_name << " - FAILED" << std::endl;
239  }
240 }
241 
242 #define QUOTEME(x) #x
243 #define TEST_PERFORMANCE0(filter, params, test_class) run_test< test_class >(filter, params, QUOTEME(test_class))
244 #define TEST_PERFORMANCE1(filter, params, test_class, a0) run_test< test_class<a0> >(filter, params, QUOTEME(test_class<a0>))
245 #define TEST_PERFORMANCE2(filter, params, test_class, a0, a1) run_test< test_class<a0, a1> >(filter, params, QUOTEME(test_class) "<" QUOTEME(a0) ", " QUOTEME(a1) ">")
246 #define TEST_PERFORMANCE3(filter, params, test_class, a0, a1, a2) run_test< test_class<a0, a1, a2> >(filter, params, QUOTEME(test_class) "<" QUOTEME(a0) ", " QUOTEME(a1) ", " QUOTEME(a2) ">")
247 #define TEST_PERFORMANCE4(filter, params, test_class, a0, a1, a2, a3) run_test< test_class<a0, a1, a2, a3> >(filter, params, QUOTEME(test_class) "<" QUOTEME(a0) ", " QUOTEME(a1) ", " QUOTEME(a2) ", " QUOTEME(a3) ">")
248 #define TEST_PERFORMANCE5(filter, params, test_class, a0, a1, a2, a3, a4) run_test< test_class<a0, a1, a2, a3, a4> >(filter, params, QUOTEME(test_class) "<" QUOTEME(a0) ", " QUOTEME(a1) ", " QUOTEME(a2) ", " QUOTEME(a3) ", " QUOTEME(a4) ">")
249 #define TEST_PERFORMANCE6(filter, params, test_class, a0, a1, a2, a3, a4, a5) run_test< test_class<a0, a1, a2, a3, a4, a5> >(filter, params, QUOTEME(test_class) "<" QUOTEME(a0) ", " QUOTEME(a1) ", " QUOTEME(a2) ", " QUOTEME(a3) ", " QUOTEME(a4) ", " QUOTEME(a5) ">")
void start()
Definition: performance_tests.h:54
double stddev
Definition: timings.h:15
clock::time_point m_base
Definition: performance_tests.h:66
bool run()
Definition: performance_tests.h:89
const uint32_t T[512]
Definition: groestl_tables.h:36
double get_non_parametric_skew() const
Definition: performance_tests.h:133
void add(const char *name, const instance &data)
Definition: timings.cc:122
uint64_t get_max() const
Definition: performance_tests.h:129
int * count
Definition: gmock_stress_test.cc:176
int i
Definition: pymoduletest.py:23
::std::string string
Definition: gtest-port.h:1097
Definition: test.py:1
size_t get_size() const
Definition: performance_tests.h:120
double mean
Definition: timings.h:15
bool verbose
Definition: performance_tests.h:73
volatile uint64_t m_warm_up
! This field is intended for preclude compiler optimizations
Definition: performance_tests.h:157
test_runner(const Params &params)
Definition: performance_tests.h:82
TimingsDatabase td
Definition: performance_tests.h:72
Definition: timings.h:8
int elapsed_ms()
Definition: performance_tests.h:59
int time_per_call(int scale=1) const
Definition: performance_tests.h:122
bool stats
Definition: performance_tests.h:74
Definition: performance_tests.h:70
match
Definition: check_missing_rpc_methods.py:38
unsigned loop_multiplier
Definition: performance_tests.h:75
std::vector< instance > get(const char *name) const
Definition: timings.cc:112
uint64_t get_min() const
Definition: performance_tests.h:128
uint64_t warm_up()
Definition: performance_tests.h:145
size_t npoints
Definition: timings.h:14
void run_test(const std::string &filter, Params &params, const char *test_name)
Definition: performance_tests.h:165
double get_mean() const
Definition: performance_tests.h:130
double get_stddev() const
Definition: performance_tests.h:132
Params m_params
Definition: performance_tests.h:159
std::vector< uint64_t > get_quantiles(size_t n) const
Definition: performance_tests.h:134
#define min(a, b)
Definition: oaes_lib.c:78
clock::time_point m_start
Definition: performance_tests.h:67
performance_timer()
Definition: performance_tests.h:49
unsigned __int64 uint64_t
Definition: stdint.h:136
boost::chrono::high_resolution_clock clock
Definition: performance_tests.h:47
const base::type::char_t * unit
Definition: easylogging++.h:798
std::vector< tools::PerformanceTimer > m_per_call_timers
Definition: performance_tests.h:160
Definition: stats.h:6
Definition: performance_tests.h:79
bool is_same_distribution(size_t npoints, double mean, double stddev) const
Definition: performance_tests.h:136
Definition: performance_tests.h:44
time
Definition: gen_wide_data.py:40
int m_elapsed
Definition: performance_tests.h:158
std::unique_ptr< Stats< tools::PerformanceTimer, uint64_t > > m_stats
Definition: performance_tests.h:161
std::string to_string(t_connection_type type)
Definition: connection_basic.cpp:70
Definition: timings.h:11
int elapsed_time() const
Definition: performance_tests.h:119
uint64_t get_median() const
Definition: performance_tests.h:131
int test(const char *portListingXml, int portListingXmlLen, const struct port_mapping *ref, int count)
Definition: testportlistingparse.c:26