Chebyshev
Unit testing for scientific software
Loading...
Searching...
No Matches
benchmark.h
Go to the documentation of this file.
1
5
6#ifndef CHEBYSHEV_BENCHMARK_H
7#define CHEBYSHEV_BENCHMARK_H
8
9#include <ctime>
10#include <iostream>
11#include <thread>
12#include <future>
13#include <memory>
14
15#include "./core/random.h"
16#include "./core/output.h"
17#include "./benchmark/timer.h"
20
21
22namespace chebyshev {
23
24
31namespace benchmark {
32
33
43 template<typename InputType, typename Function>
44 inline long double runtime(
45 Function func, const std::vector<InputType>& input) {
46
47 if (input.size() == 0)
48 return 0.0;
49
50 // Dummy variable
51 __volatile__ auto c = func(input[0]);
52
53 timer t = timer();
54
55 for (unsigned int j = 0; j < input.size(); ++j)
56 c += func(input[j]);
57
58 return t();
59 }
60
61
65
67 std::string moduleName;
68
71
74
76 std::vector<std::string> outputFiles {};
77
80 std::map<std::string, bool> pickedBenchmarks {};
81
84 std::vector<std::string> benchmarkOutputFiles {};
85
87 std::vector<std::string> benchmarkColumns = {
88 "name", "averageRuntime", "stdevRuntime", "runsPerSecond"
89 };
90
96 bool multithreading = true;
97
98 };
99
100
104
105 private:
106
108 std::map <std::string, std::vector<benchmark_result>> benchmarkResults {};
109
111 std::mutex benchmarkMutex;
112
114 std::vector<std::thread> benchmarkThreads {};
115
118 bool wasTerminated {false};
119
120 public:
121
124
127 std::shared_ptr<output::output_context> output;
128
131 std::shared_ptr<random::random_context> random;
132
133
140 inline void setup(
141 std::string moduleName,
142 int argc = 0,
143 const char** argv = nullptr) {
144
145 // Initialize other modules
147 output = std::make_shared<output::output_context>();
148 random = std::make_shared<random::random_context>();
149
150 // Initialize list of picked tests
151 if(argc && argv)
152 for (int i = 1; i < argc; ++i)
153 settings.pickedBenchmarks[argv[i]] = true;
154
155 output->info("Starting benchmarks of the " + moduleName + " module ...");
156
157 settings.moduleName = moduleName;
158 benchmarkResults.clear();
159 wasTerminated = false;
160 }
161
162
167 inline void terminate(bool exit = false) {
168
169 // Ensure that all benchmarks have been run
170 this->wait_results();
171
172 std::lock_guard<std::mutex> lock(benchmarkMutex);
173
174 unsigned int totalBenchmarks = 0;
175 unsigned int failedBenchmarks = 0;
176
177 for (const auto& pair : benchmarkResults) {
178 for (const auto& testCase : pair.second) {
179 totalBenchmarks++;
180 failedBenchmarks += testCase.failed ? 1 : 0;
181 }
182 }
183
184
185 // Ensure that an output file is specified
186 if( output->settings.outputToFile &&
187 !output->settings.outputFiles.size() &&
189 !settings.outputFiles.size()) {
190
191 settings.outputFiles = { settings.moduleName + "_results" };
192 }
193
194
195 // Print benchmark results
196 std::vector<std::string> outputFiles;
197 outputFiles = settings.outputFiles;
198 outputFiles.insert(
199 outputFiles.end(),
202 );
203
204 output->print_results(
205 benchmarkResults,
207 outputFiles
208 );
209
210
211 // Print overall test results
212 double percent = totalBenchmarks > 0 ? (failedBenchmarks / (double) totalBenchmarks) * 100 : 0;
213 output->info("Finished benchmarks of the " + settings.moduleName);
214 output->info(
215 std::to_string(totalBenchmarks) + " total benchmarks, " +
216 std::to_string(failedBenchmarks) + " failed (" + std::to_string(percent).substr(0, 4) + "%)"
217 );
218
219 if (totalBenchmarks == 0)
220 output->warn("No benchmarks were executed!");
221
222 if(exit) {
223 output->terminate();
224 std::exit(failedBenchmarks);
225 }
226
227 wasTerminated = true;
228 }
229
230
233 std::string moduleName, int argc = 0,
234 const char** argv = nullptr) {
235
236 setup(moduleName, argc, argv);
237 }
238
239
242
243 if (!wasTerminated)
244 terminate();
245 }
246
247
250
251 std::lock_guard<std::mutex> lock(benchmarkMutex);
252 benchmarkResults = other.benchmarkResults;
253 settings = other.settings;
254 output = other.output;
255 random = other.random;
256 }
257
258
261
262 std::lock_guard<std::mutex> lock(benchmarkMutex);
263 benchmarkResults = other.benchmarkResults;
264 settings = other.settings;
265 output = other.output;
266 random = other.random;
267
268 return *this;
269 }
270
271
282 template<typename InputType = double, typename Function>
283 inline void benchmark(
284 const std::string& name,
285 Function func,
286 const std::vector<InputType>& input,
287 unsigned int runs = 0,
288 bool quiet = false) {
289
290 if (runs == 0)
291 runs = settings.defaultRuns;
292
293 output->debug("Running benchmark: " + name);
294
295 auto task = [this, name, func, input, runs, quiet]() {
296
297 // Whether the benchmark failed because of an exception
298 bool failed = false;
299
300 // Running average
301 prec_t averageRuntime;
302
303 // Running total sum of squares
304 prec_t sumSquares;
305
306 // Total runtime
307 prec_t totalRuntime;
308
309 try {
310
311 // Use Welford's algorithm to compute
312 // the average and the variance
313 totalRuntime = runtime(func, input);
314 averageRuntime = totalRuntime / input.size();
315 sumSquares = 0.0;
316
317 for (unsigned int i = 1; i < runs; ++i) {
318
319 // Compute the runtime for a single run
320 // and update the running estimates
321 const prec_t currentRun = runtime(func, input);
322 const prec_t currentAverage = currentRun / input.size();
323 totalRuntime += currentRun;
324
325 const prec_t tmp = averageRuntime;
326 averageRuntime = tmp + (currentAverage - tmp) / (i + 1);
327 sumSquares += (currentAverage - tmp)
328 * (currentAverage - averageRuntime);
329 }
330
331 } catch(...) {
332
333 // Catch any exception and mark the benchmark as failed
334 failed = true;
335 }
336
337 benchmark_result res {};
338 res.name = name;
339 res.runs = runs;
340 res.iterations = input.size();
341 res.totalRuntime = totalRuntime;
342 res.averageRuntime = averageRuntime;
343 res.runsPerSecond = 1000.0 / res.averageRuntime;
344 res.failed = failed;
345 res.quiet = quiet;
346
347 if (runs > 1)
348 res.stdevRuntime = std::sqrt(sumSquares / (runs - 1));
349
350 std::lock_guard<std::mutex> lock(benchmarkMutex);
351 benchmarkResults[name].push_back(res);
352 };
353
355 benchmarkThreads.emplace_back(task) : task();
356 }
357
358
365 template <typename InputType = double, typename Function>
366 inline void benchmark(
367 const std::string& name,
368 Function func,
370
371 if (opt.runs == 0)
373
374 output->debug("Running benchmark: " + name);
375
376 // Generate input set
377 random::random_source rnd (opt.seed);
378
379 if (opt.seed == 0)
380 rnd = random->get_rnd();
381
382 std::vector<InputType> input (opt.iterations);
383
384 for (unsigned int i = 0; i < opt.iterations; ++i)
385 input[i] = opt.inputGenerator(rnd);
386
387 uint64_t seed = rnd.get_seed();
388
389 // Package task for multi-threaded execution
390 auto task = [this, input, name, func, opt, seed]() {
391
392 // Whether the benchmark failed because of an exception
393 bool failed = false;
394
395 // Running average
396 prec_t averageRuntime;
397
398 // Running total sum of squares
399 prec_t sumSquares;
400
401 // Total runtime
402 prec_t totalRuntime;
403
404 try {
405
406 // Use Welford's algorithm to compute
407 // the average and the variance
408 totalRuntime = runtime(func, input);
409 averageRuntime = totalRuntime / input.size();
410 sumSquares = 0.0;
411
412 for (unsigned int i = 1; i < opt.runs; ++i) {
413
414 // Compute the runtime for a single run
415 // and update the running estimates
416 const prec_t currentRun = runtime(func, input);
417 const prec_t currentAverage = currentRun / input.size();
418 totalRuntime += currentRun;
419
420 const prec_t tmp = averageRuntime;
421 averageRuntime = tmp + (currentAverage - tmp) / (i + 1);
422 sumSquares += (currentAverage - tmp)
423 * (currentAverage - averageRuntime);
424 }
425
426 } catch(...) {
427
428 // Catch any exception and mark the benchmark as failed
429 failed = true;
430 }
431
432 benchmark_result res {};
433 res.name = name;
434 res.runs = opt.runs;
435 res.iterations = input.size();
436 res.totalRuntime = totalRuntime;
437 res.averageRuntime = averageRuntime;
438 res.runsPerSecond = 1000.0 / res.averageRuntime;
439 res.failed = failed;
440 res.quiet = opt.quiet;
441 res.seed = seed;
442
443 if (opt.runs > 1)
444 res.stdevRuntime = std::sqrt(sumSquares / (opt.runs - 1));
445
446 std::lock_guard<std::mutex> lock(benchmarkMutex);
447 benchmarkResults[name].push_back(res);
448 };
449
451 benchmarkThreads.emplace_back(task) : task();
452 }
453
454
463 template<typename InputType = double, typename Function>
464 inline void benchmark(
465 const std::string& name,
466 Function func,
467 InputGenerator<InputType> inputGenerator,
468 unsigned int runs = 0,
469 unsigned int iterations = 0,
470 bool quiet = false) {
471
472 if (runs == 0)
473 runs = settings.defaultRuns;
474
475 if (iterations == 0)
476 iterations = settings.defaultIterations;
477
479 opt.runs = runs;
480 opt.iterations = iterations;
481 opt.inputGenerator = inputGenerator;
482 opt.quiet = quiet;
483
484 benchmark(name, func, opt);
485 }
486
487
489 inline void wait_results() {
490
491 for (auto& t : benchmarkThreads)
492 if (t.joinable())
493 t.join();
494
495 benchmarkThreads.clear();
496 }
497
498
505 inline std::vector<benchmark_result> get_benchmark(const std::string& name) {
506
507 this->wait_results();
508 return benchmarkResults[name];
509 }
510
511
518 inline benchmark_result get_benchmark(const std::string& name, unsigned int i) {
519
520 this->wait_results();
521 return benchmarkResults[name].at(i);
522 }
523
524 };
525
526
532 benchmark_context make_context(const std::string& moduleName,
533 int argc = 0, const char** argv = nullptr) {
534
535 return benchmark_context(moduleName, argc, argv);
536 }
537
538}}
539
540#endif
Structures for the benchmark module.
Benchmark module context, handling benchmark requests concurrently.
Definition benchmark.h:103
void wait_results()
Wait for all concurrent benchmarks to finish execution.
Definition benchmark.h:489
benchmark_context(std::string moduleName, int argc=0, const char **argv=nullptr)
Default constructor setting up the context.
Definition benchmark.h:232
benchmark_context(const benchmark_context &other)
Custom copy constructor to avoid copying std::mutex.
Definition benchmark.h:249
void benchmark(const std::string &name, Function func, benchmark_options< InputType > opt)
Run a benchmark on a generic function, with the given options.
Definition benchmark.h:366
benchmark_context & operator=(const benchmark_context &other)
Custom assignment operator to avoid copying std::mutex.
Definition benchmark.h:260
void setup(std::string moduleName, int argc=0, const char **argv=nullptr)
Setup the benchmark environment.
Definition benchmark.h:140
void benchmark(const std::string &name, Function func, InputGenerator< InputType > inputGenerator, unsigned int runs=0, unsigned int iterations=0, bool quiet=false)
Run a benchmark on a generic function, with the given argument options.
Definition benchmark.h:464
std::shared_ptr< random::random_context > random
Random module settings for the context, dynamically allocated and possibly shared between multiple co...
Definition benchmark.h:131
std::vector< benchmark_result > get_benchmark(const std::string &name)
Get a list of benchmarks results associated to the given name or label.
Definition benchmark.h:505
benchmark_result get_benchmark(const std::string &name, unsigned int i)
Get a benchmark result associated to the given name or label and index.
Definition benchmark.h:518
void benchmark(const std::string &name, Function func, const std::vector< InputType > &input, unsigned int runs=0, bool quiet=false)
Run a benchmark on a generic function, with the given input vector.
Definition benchmark.h:283
benchmark_settings settings
Settings for the benchmark context.
Definition benchmark.h:123
~benchmark_context()
Terminate the benchmark module.
Definition benchmark.h:241
std::shared_ptr< output::output_context > output
Output module settings for the context, dynamically allocated and possibly shared between multiple co...
Definition benchmark.h:127
void terminate(bool exit=false)
Terminate the benchmarking environment.
Definition benchmark.h:167
Timer class to measure elapsed time in milliseconds.
Definition timer.h:18
long double prec_t
Floating-point type of higher precision, used in computations, such as error estimation.
Definition common.h:42
#define CHEBYSHEV_BENCHMARK_ITER
Default number of benchmark iterations.
Definition common.h:22
#define CHEBYSHEV_BENCHMARK_RUNS
Default number of benchmark runs.
Definition common.h:27
Input generators for benchmarks.
long double runtime(Function func, const std::vector< InputType > &input)
Measure the total runtime of a function over the given input for a single run.
Definition benchmark.h:44
std::function< InputType(random::random_source &)> InputGenerator
A function which takes in a random source and returns a generated input element.
Definition benchmark_structures.h:65
benchmark_context make_context(const std::string &moduleName, int argc=0, const char **argv=nullptr)
Construct a benchmarking context with the given parameters.
Definition benchmark.h:532
General namespace of the framework.
Definition benchmark.h:22
The output module, with formatting capabilities.
The pseudorandom number generation and sampling module.
A structure holding the options of a benchmark.
Definition benchmark_structures.h:71
unsigned int iterations
Number of iterations.
Definition benchmark_structures.h:77
uint64_t seed
The seed to use for randomized input generation (by default, a random seed is generated using the ran...
Definition benchmark_structures.h:87
bool quiet
Whether to print to standard output or not.
Definition benchmark_structures.h:83
unsigned int runs
Number of runs (run with the same input values).
Definition benchmark_structures.h:74
InputGenerator< InputType > inputGenerator
The function to use to generate input for the benchmark.
Definition benchmark_structures.h:80
Structure holding the results of a benchmark.
Definition benchmark_structures.h:23
std::string name
Identifying name of the function or test case.
Definition benchmark_structures.h:26
Global settings of the benchmark module, used in benchmark_context.
Definition benchmark.h:64
std::vector< std::string > benchmarkOutputFiles
The files to write benchmark results to (if empty, all results are output to a generic file).
Definition benchmark.h:84
unsigned int defaultRuns
Default number of runs.
Definition benchmark.h:73
std::map< std::string, bool > pickedBenchmarks
Target benchmarks marked for execution (all benchmarks will be executed if empty)
Definition benchmark.h:80
std::vector< std::string > benchmarkColumns
Default columns to print for benchmarks.
Definition benchmark.h:87
std::string moduleName
Name of the module currently being benchmarked.
Definition benchmark.h:67
bool multithreading
Whether to use multithreading for the execution of benchmarks (defaults to true).
Definition benchmark.h:96
unsigned int defaultIterations
Default number of iterations.
Definition benchmark.h:70
std::vector< std::string > outputFiles
The files to write all benchmark results to.
Definition benchmark.h:76
A source of pseudorandom numbers.
Definition random.h:39
uint64_t get_seed()
Get the seed used to generate the random source.
Definition random.h:59
A timer class to measure elapsed time in milliseconds.