Monero
Loading...
Searching...
No Matches
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{
46public:
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
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
65private:
66 clock::time_point m_base;
67 clock::time_point m_start;
68};
69
70struct Params
71{
73 bool verbose;
74 bool stats;
76};
77
78template <typename T>
80{
81public:
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
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
141private:
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
156private:
160 std::vector<tools::PerformanceTimer> m_per_call_timers;
161 std::unique_ptr<Stats<tools::PerformanceTimer, uint64_t>> m_stats;
162};
163
164template <typename T>
165void 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 }
230cmp += " -- " + 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) ">")
Definition stats.h:7
Definition timings.h:9
void add(const char *name, const instance &data)
Definition timings.cc:122
std::vector< instance > get(const char *name) const
Definition timings.cc:112
Definition performance_tests.h:45
void start()
Definition performance_tests.h:54
clock::time_point m_base
Definition performance_tests.h:66
performance_timer()
Definition performance_tests.h:49
int elapsed_ms()
Definition performance_tests.h:59
clock::time_point m_start
Definition performance_tests.h:67
boost::chrono::high_resolution_clock clock
Definition performance_tests.h:47
Definition performance_tests.h:80
Params m_params
Definition performance_tests.h:159
uint64_t get_min() const
Definition performance_tests.h:128
uint64_t get_median() const
Definition performance_tests.h:131
std::unique_ptr< Stats< tools::PerformanceTimer, uint64_t > > m_stats
Definition performance_tests.h:161
test_runner(const Params &params)
Definition performance_tests.h:82
int time_per_call(int scale=1) const
Definition performance_tests.h:122
double get_stddev() const
Definition performance_tests.h:132
std::vector< tools::PerformanceTimer > m_per_call_timers
Definition performance_tests.h:160
double get_non_parametric_skew() const
Definition performance_tests.h:133
volatile uint64_t m_warm_up
! This field is intended for preclude compiler optimizations
Definition performance_tests.h:157
uint64_t warm_up()
Definition performance_tests.h:145
int elapsed_time() const
Definition performance_tests.h:119
std::vector< uint64_t > get_quantiles(size_t n) const
Definition performance_tests.h:134
size_t get_size() const
Definition performance_tests.h:120
bool run()
Definition performance_tests.h:89
uint64_t get_max() const
Definition performance_tests.h:129
bool is_same_distribution(size_t npoints, double mean, double stddev) const
Definition performance_tests.h:136
double get_mean() const
Definition performance_tests.h:130
int m_elapsed
Definition performance_tests.h:158
Definition test.py:1
#define min(a, b)
Definition oaes_lib.c:78
void run_test(const std::string &filter, Params &params, const char *test_name)
Definition performance_tests.h:165
unsigned __int64 uint64_t
Definition stdint.h:136
Definition performance_tests.h:71
unsigned loop_multiplier
Definition performance_tests.h:75
bool stats
Definition performance_tests.h:74
TimingsDatabase td
Definition performance_tests.h:72
bool verbose
Definition performance_tests.h:73
Definition timings.h:12
double mean
Definition timings.h:15
size_t npoints
Definition timings.h:14
double stddev
Definition timings.h:15
#define T(x)