1 /// Parameterizable benchmarking with support for parallel, global results, reports, etc. 2 module tern.benchmark; 3 4 import tern.traits; 5 import tern.meta; 6 import std.algorithm; 7 import std.parallelism; 8 import std.datetime; 9 import std.stdio; 10 import std.conv; 11 import std.range; 12 13 public struct BenchmarkConfig 14 { 15 public: 16 final: 17 size_t warmup = 100; 18 size_t iterations = 1000; 19 bool parallel = false; 20 } 21 22 public struct BenchmarkResult 23 { 24 public: 25 final: 26 string functionName; 27 size_t index; 28 Duration duration; 29 BenchmarkConfig config; 30 } 31 32 private: 33 BenchmarkResult[] results; 34 35 public: 36 /** 37 * Benchmarks all functions in `FUNCS` with the given config. 38 * 39 * Params: 40 * FUNCS = Sequence of functions to be benchmarked. 41 * config = Benchmark configuration. 42 * 43 * Remarks: 44 * May not be parameterized. 45 */ 46 BenchmarkResult[] benchmark(FUNCS...)(const scope BenchmarkConfig config) 47 if (allSatisfy!(isCallable, FUNCS)) 48 { 49 BenchmarkResult[FUNCS.length] ret; 50 if (!config.parallel) 51 { 52 foreach (i, F; FUNCS) 53 { 54 auto timestamp = Clock.currTime; 55 writeln("[", timestamp.hour, ":", timestamp.minute, ":", timestamp.second, "] ", identifier!F, "() benchmarking..."); 56 57 foreach (j; 0..config.warmup) 58 F(); 59 60 auto start = Clock.currTime; 61 foreach (j; 0..config.iterations) 62 F(); 63 64 auto duration = (Clock.currTime - start); 65 ret[i] = BenchmarkResult(identifier!F~"()", i, duration, config); 66 results ~= BenchmarkResult(identifier!F~"()", results.length, duration, config); 67 68 timestamp = Clock.currTime; 69 writeln("[", timestamp.hour, ":", timestamp.minute, ":", timestamp.second, "] ", identifier!F, "() finished benchmark!"); 70 } 71 } 72 else 73 { 74 foreach (i; parallel(iota(0, FUNCS.length))) 75 { 76 static foreach (j, F; FUNCS) 77 mixin("if (i == "~j.to!string~") 78 { 79 auto timestamp = Clock.currTime; 80 writeln(\"[\", timestamp.hour, \":\", timestamp.minute, \":\", timestamp.second, \"] \", identifier!F, \"() benchmarking...\"); 81 foreach (k; 0..config.warmup) 82 FUNCS["~j.to!string~"](); 83 84 auto start = Clock.currTime; 85 foreach (k; 0..config.iterations) 86 FUNCS["~j.to!string~"](); 87 88 auto duration = (Clock.currTime - start); 89 ret[i] = BenchmarkResult(identifier!F~\"()\", i, duration, config); 90 results ~= BenchmarkResult(identifier!F~\"()\", results.length, duration, config); 91 92 timestamp = Clock.currTime; 93 writeln(\"[\", timestamp.hour, \":\", timestamp.minute, \":\", timestamp.second, \"] \", identifier!F, \"() finished benchmark!\"); 94 }"); 95 } 96 } 97 return ret.dup; 98 } 99 100 /** 101 * Benchmarks `F` with the given config and arguments. 102 * 103 * Params: 104 * F = The function to be benchmarked. 105 * config = Benchmark configuration. 106 * args = The arguments to invoke `F` with. 107 */ 108 BenchmarkResult[] benchmark(alias F, ARGS...)(const scope BenchmarkConfig config, ARGS args) 109 { 110 auto timestamp = Clock.currTime; 111 writeln("[", timestamp.hour, ":", timestamp.minute, ":", timestamp.second, "] ", SignatureOf!F, " benchmarking..."); 112 113 foreach (i; 0..config.warmup) 114 F(args); 115 116 auto start = Clock.currTime; 117 foreach (i; 0..config.iterations) 118 F(args); 119 120 auto duration = (Clock.currTime - start); 121 auto result = BenchmarkResult(SignatureOf!F, 0, duration, config); 122 results ~= BenchmarkResult(SignatureOf!F, results.length, duration, config); 123 124 timestamp = Clock.currTime; 125 writeln("[", timestamp.hour, ":", timestamp.minute, ":", timestamp.second, "] ", SignatureOf!F, " finished benchmark!"); 126 127 return [result]; 128 } 129 130 /** 131 * Writes a report of `results` to the console. 132 * 133 * Params: 134 * results = The benchmark results to be written. 135 */ 136 void report(BenchmarkResult[] results) 137 { 138 writeln("BENCHMARK ["~__VENDOR__~" "~__VERSION__.to!string~"]"); 139 writeln("-----------------------------------------------------------------------------"); 140 writeln("| Index | Function | Duration |"); 141 writeln("-----------------------------------------------------------------------------"); 142 143 foreach (res; results) 144 { 145 auto duration = res.duration.split!("seconds", "msecs", "usecs"); 146 147 writef("| %-6s| %-40s | ", res.index, res.functionName); 148 149 if (duration.seconds > 1) 150 writefln("%-10.3fs |", duration.seconds); 151 else if (duration.msecs > 1) 152 writefln("%-10.3fms |", duration.msecs); 153 else 154 writefln("%-10.3fus |", duration.usecs); 155 } 156 157 writeln("-----------------------------------------------------------------------------"); 158 } 159 160 /// Writes a report of all benchmarks that have been run to the console. 161 void reportAll() 162 { 163 report(results); 164 }