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 std::cout << "Starting benchmarks of the ";
156 std::cout << moduleName << " module ..." << std::endl;
157
158 settings.moduleName = moduleName;
159 benchmarkResults.clear();
160 wasTerminated = false;
161 }
162
163
168 inline void terminate(bool exit = false) {
169
170 // Ensure that all benchmarks have been run
171 this->wait_results();
172
173 std::lock_guard<std::mutex> lock(benchmarkMutex);
174
175 unsigned int totalBenchmarks = 0;
176 unsigned int failedBenchmarks = 0;
177
178 for (const auto& pair : benchmarkResults) {
179 for (const auto& testCase : pair.second) {
180 totalBenchmarks++;
181 failedBenchmarks += testCase.failed ? 1 : 0;
182 }
183 }
184
185
186 // Ensure that an output file is specified
187 if( output->settings.outputToFile &&
188 !output->settings.outputFiles.size() &&
190 !settings.outputFiles.size()) {
191
192 settings.outputFiles = { settings.moduleName + "_results" };
193 }
194
195
196 // Print benchmark results
197 std::vector<std::string> outputFiles;
198 outputFiles = settings.outputFiles;
199 outputFiles.insert(
200 outputFiles.end(),
203 );
204
205 output->print_results(
206 benchmarkResults,
208 outputFiles
209 );
210
211
212 // Print overall test results
213 std::cout << "Finished testing " << settings.moduleName << '\n';
214 std::cout << totalBenchmarks << " total tests, ";
215 std::cout << failedBenchmarks << " failed";
216
217 // Print proportion of failed test, avoiding division by zero
218 if (totalBenchmarks > 0) {
219
220 const double percent = (failedBenchmarks / (double) totalBenchmarks) * 100;
221 std::cout << " (" << std::setprecision(3) << percent << "%)" << std::endl;
222
223 } else {
224 std::cout << "\nNo benchmarks were run!" << std::endl;
225 }
226
227 if(exit) {
228 output->terminate();
229 std::exit(failedBenchmarks);
230 }
231
232 wasTerminated = true;
233 }
234
235
238 std::string moduleName, int argc = 0,
239 const char** argv = nullptr) {
240
241 setup(moduleName, argc, argv);
242 }
243
244
247
248 if (!wasTerminated)
249 terminate();
250 }
251
252
255
256 std::lock_guard<std::mutex> lock(benchmarkMutex);
257 benchmarkResults = other.benchmarkResults;
258 settings = other.settings;
259 output = other.output;
260 random = other.random;
261 }
262
263
266
267 std::lock_guard<std::mutex> lock(benchmarkMutex);
268 benchmarkResults = other.benchmarkResults;
269 settings = other.settings;
270 output = other.output;
271 random = other.random;
272
273 return *this;
274 }
275
276
287 template<typename InputType = double, typename Function>
288 inline void benchmark(
289 const std::string& name,
290 Function func,
291 const std::vector<InputType>& input,
292 unsigned int runs = 0,
293 bool quiet = false) {
294
295 if (runs == 0)
296 runs = settings.defaultRuns;
297
298 auto task = [this, name, func, input, runs, quiet]() {
299
300 // Whether the benchmark failed because of an exception
301 bool failed = false;
302
303 // Running average
304 long double averageRuntime;
305
306 // Running total sum of squares
307 long double sumSquares;
308
309 // Total runtime
310 long double totalRuntime;
311
312 try {
313
314 // Use Welford's algorithm to compute
315 // the average and the variance
316 totalRuntime = runtime(func, input);
317 averageRuntime = totalRuntime / input.size();
318 sumSquares = 0.0;
319
320 for (unsigned int i = 1; i < runs; ++i) {
321
322 // Compute the runtime for a single run
323 // and update the running estimates
324 const long double currentRun = runtime(func, input);
325 const long double currentAverage = currentRun / input.size();
326 totalRuntime += currentRun;
327
328 const long double tmp = averageRuntime;
329 averageRuntime = tmp + (currentAverage - tmp) / (i + 1);
330 sumSquares += (currentAverage - tmp)
331 * (currentAverage - averageRuntime);
332 }
333
334 } catch(...) {
335
336 // Catch any exception and mark the benchmark as failed
337 failed = true;
338 }
339
340 benchmark_result res {};
341 res.name = name;
342 res.runs = runs;
343 res.iterations = input.size();
344 res.totalRuntime = totalRuntime;
345 res.averageRuntime = averageRuntime;
346 res.runsPerSecond = 1000.0 / res.averageRuntime;
347 res.failed = failed;
348 res.quiet = quiet;
349
350 if (runs > 1)
351 res.stdevRuntime = std::sqrt(sumSquares / (runs - 1));
352
353 std::lock_guard<std::mutex> lock(benchmarkMutex);
354 benchmarkResults[name].push_back(res);
355 };
356
358 benchmarkThreads.emplace_back(task) : task();
359 }
360
361
368 template <typename InputType = double, typename Function>
369 inline void benchmark(
370 const std::string& name,
371 Function func,
373
374 if (opt.runs == 0)
376
377 // Generate input set
378 random::random_source rnd (opt.seed);
379
380 if (opt.seed == 0)
381 rnd = random->get_rnd();
382
383 std::vector<InputType> input (opt.iterations);
384
385 for (unsigned int i = 0; i < opt.iterations; ++i)
386 input[i] = opt.inputGenerator(rnd);
387
388 uint64_t seed = rnd.get_seed();
389
390 // Package task for multi-threaded execution
391 benchmarkThreads.emplace_back([this, input, name, func, opt, seed]() {
392
393 // Whether the benchmark failed because of an exception
394 bool failed = false;
395
396 // Running average
397 long double averageRuntime;
398
399 // Running total sum of squares
400 long double sumSquares;
401
402 // Total runtime
403 long double totalRuntime;
404
405 try {
406
407 // Use Welford's algorithm to compute
408 // the average and the variance
409 totalRuntime = runtime(func, input);
410 averageRuntime = totalRuntime / input.size();
411 sumSquares = 0.0;
412
413 for (unsigned int i = 1; i < opt.runs; ++i) {
414
415 // Compute the runtime for a single run
416 // and update the running estimates
417 const long double currentRun = runtime(func, input);
418 const long double currentAverage = currentRun / input.size();
419 totalRuntime += currentRun;
420
421 const long double tmp = averageRuntime;
422 averageRuntime = tmp + (currentAverage - tmp) / (i + 1);
423 sumSquares += (currentAverage - tmp)
424 * (currentAverage - averageRuntime);
425 }
426
427 } catch(...) {
428
429 // Catch any exception and mark the benchmark as failed
430 failed = true;
431 }
432
433 benchmark_result res {};
434 res.name = name;
435 res.runs = opt.runs;
436 res.iterations = input.size();
437 res.totalRuntime = totalRuntime;
438 res.averageRuntime = averageRuntime;
439 res.runsPerSecond = 1000.0 / res.averageRuntime;
440 res.failed = failed;
441 res.quiet = opt.quiet;
442 res.seed = seed;
443
444 if (opt.runs > 1)
445 res.stdevRuntime = std::sqrt(sumSquares / (opt.runs - 1));
446
447 std::lock_guard<std::mutex> lock(benchmarkMutex);
448 benchmarkResults[name].push_back(res);
449 });
450 }
451
452
461 template<typename InputType = double, typename Function>
462 inline void benchmark(
463 const std::string& name,
464 Function func,
465 InputGenerator<InputType> inputGenerator,
466 unsigned int runs = 0,
467 unsigned int iterations = 0,
468 bool quiet = false) {
469
470 if (runs == 0)
471 runs = settings.defaultRuns;
472
473 if (iterations == 0)
474 iterations = settings.defaultIterations;
475
477 opt.runs = runs;
478 opt.iterations = iterations;
479 opt.inputGenerator = inputGenerator;
480 opt.quiet = quiet;
481
482 benchmark(name, func, opt);
483 }
484
485
487 inline void wait_results() {
488
489 for (auto& t : benchmarkThreads)
490 if (t.joinable())
491 t.join();
492
493 benchmarkThreads.clear();
494 }
495
496
503 inline std::vector<benchmark_result> get_benchmark(const std::string& name) {
504
505 this->wait_results();
506 return benchmarkResults[name];
507 }
508
509
516 inline benchmark_result get_benchmark(const std::string& name, unsigned int i) {
517
518 this->wait_results();
519 return benchmarkResults[name].at(i);
520 }
521
522 };
523
524
530 benchmark_context make_context(const std::string& moduleName,
531 int argc = 0, const char** argv = nullptr) {
532
533 return benchmark_context(moduleName, argc, argv);
534 }
535
536}}
537
538#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:487
benchmark_context(std::string moduleName, int argc=0, const char **argv=nullptr)
Default constructor setting up the context.
Definition benchmark.h:237
benchmark_context(const benchmark_context &other)
Custom copy constructor to avoid copying std::mutex.
Definition benchmark.h:254
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:369
benchmark_context & operator=(const benchmark_context &other)
Custom assignment operator to avoid copying std::mutex.
Definition benchmark.h:265
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:462
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:503
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:516
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:288
benchmark_settings settings
Settings for the benchmark context.
Definition benchmark.h:123
~benchmark_context()
Terminate the benchmark module.
Definition benchmark.h:246
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:168
Timer class to measure elapsed time in milliseconds.
Definition timer.h:18
#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:530
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.