1 /** 2 benchmark module: implmentation of the benchmarking utilities. 3 Copyright: Copyright Nouredine Hussain 2017. 4 Authors: Nouredine Hussain 5 License: $(LINK3 http://www.boost.org/LICENSE_1_0.txt, Boost Software License - Version 1.0). 6 */ 7 8 module simplebench; 9 10 import stats; 11 import std.json; 12 13 import core.time: MonoTimeImpl, ClockType, ticksToNSecs, dur; 14 15 alias MonoTime = MonoTimeImpl!(ClockType.precise); 16 17 /// This function can be used to avoid unused results from being optimized (removed) by the compiler. 18 T black_box(T)(T dummy) { 19 asm {;} 20 return dummy; 21 } 22 23 public struct Bencher { 24 25 ulong iterations; 26 long ticks; 27 ulong bytes; 28 ulong total_ns; 29 ulong total_iterations; 30 31 32 public void iter(T, Args...)(T delegate(Args) inner, Args args){ 33 auto start = MonoTime.currTime.ticks; 34 foreach(_;0..iterations){ 35 black_box(inner(args)); 36 } 37 ticks = MonoTime.currTime.ticks - start; 38 } 39 40 41 public @property long ns_per_iter() { 42 import std.algorithm : max; 43 if(iterations == 0) 44 return 0; 45 else 46 return (ticks / max(iterations, 1)).ticksToNSecs(); 47 } 48 49 void bench_n(in ulong n, void function(ref Bencher) f) { 50 iterations = n; 51 f(this); 52 } 53 54 55 /// Benchmark a function by measuring its execution time N consecutive times, where N is chosen apropiatily based on one execution of the function. 56 Summary auto_bench(void function(ref Bencher) f) { 57 import std.algorithm.comparison : max; 58 import core.checkedint; 59 60 ulong n = 1; 61 bench_n(n, f); 62 63 auto dpi = ns_per_iter(); 64 if(dpi==0){ 65 n = 1_000_000; 66 } else { 67 n = 1_000_000 / max(dpi, 1); 68 } 69 70 if(n==0) { 71 n = 1; 72 } 73 74 total_ns = 0; 75 total_iterations = 1; 76 double[50] samples = 0.0; 77 bool has_ovf = false; 78 for(;;) { 79 ulong loop_start = MonoTime.currTime.ticks; 80 81 foreach(_, ref e; samples){ 82 bench_n(n, f); 83 e = ns_per_iter; 84 } 85 if(!has_ovf){ 86 total_iterations = addu(cast(ulong)n, total_iterations, has_ovf); 87 if(has_ovf) { 88 total_iterations = 0; 89 } 90 } 91 92 winsorize(samples, 5.0); 93 auto summ = Summary(samples); 94 95 foreach(_, ref e; samples){ 96 bench_n(5*n, f); 97 e = ns_per_iter; 98 } 99 100 if(!has_ovf){ 101 total_iterations = addu(cast(ulong)5*n, total_iterations, has_ovf); 102 if(has_ovf) { 103 total_iterations = 0; 104 } 105 } 106 107 winsorize(samples, 5.0); 108 auto summ5 = Summary(samples); 109 auto loop_run = (MonoTime.currTime.ticks - loop_start).ticksToNSecs(); 110 111 112 total_ns += loop_run; 113 114 if(loop_run > dur!"msecs"(100).total!"nsecs" && summ.median_abs_dev_pct < 1.0 && 115 summ.median - summ5.median <= summ5.median_abs_dev){ 116 return summ5; 117 } 118 119 120 if(total_ns > dur!"seconds"(3).total!"nsecs") { 121 return summ5; 122 } 123 124 125 bool ovf; 126 muls(n, 10, ovf); 127 if(ovf){ 128 return summ5; 129 } else { 130 n *= 2; 131 } 132 } 133 } 134 } 135 136 public struct BenchSamples { 137 public string name; 138 public Summary ns_iter_summ; 139 public ulong mb_s; 140 public ulong total_ns; 141 public ulong total_iterations; 142 143 public JSONValue toJSON() { 144 145 JSONValue jj = ["name": name]; 146 jj.object["total_ns"] = JSONValue(total_ns); 147 jj.object["total_iterations"] = JSONValue(total_iterations); 148 jj.object["mb_s"] = JSONValue(mb_s); 149 jj.object["ns_iter_summ"] = JSONValue(ns_iter_summ.toJSON); 150 return jj; 151 } 152 } 153 154 public struct BenchmarksResults { 155 public BenchSamples[] samples; 156 157 public JSONValue toJSON() { 158 159 JSONValue[] jjs; 160 foreach(_, bs; samples){ 161 jjs ~= bs.toJSON; 162 } 163 JSONValue jj = ["BenchmarksResults": jjs]; 164 return jj; 165 } 166 } 167 168 /// Benchmark one function 169 BenchSamples benchmark(void function(ref Bencher) f) { 170 import std.algorithm.comparison: max; 171 Bencher bencher = {iterations: 0, ticks: 0, bytes: 0}; 172 173 auto ns_iter_summ = bencher.auto_bench(f); 174 ulong ns_iter = max(cast(ulong)ns_iter_summ.median, 1); 175 auto mb_s = bencher.bytes * 1000 / ns_iter; 176 auto total_ns = bencher.total_ns; 177 auto total_iterations = bencher.total_iterations; 178 179 BenchSamples bs = {ns_iter_summ: ns_iter_summ, 180 mb_s: mb_s, 181 total_iterations: total_iterations, 182 total_ns: total_ns}; 183 return bs; 184 } 185 186 string nsecsToStr(ulong n){ 187 import std.format : format; 188 189 string str; 190 string[] lunits = ["ns", "us", "msec", "sec"]; 191 192 ulong counter = 1; 193 foreach(_, ref unit; lunits){ 194 if(n < counter * 1000){ 195 str = format("%.6s %s", cast(double)n / counter, unit); 196 break; 197 } 198 counter *= 1000; 199 } 200 return str; 201 } 202 203 /// Benchmark multiple functions 204 public BenchmarksResults BenchMain(Functions...)(){ 205 import std.stdio; 206 import std.format; 207 import std.array : replicate; 208 209 BenchmarksResults br; 210 211 auto header = format("%25.25s | %30.30s | %15.15s | %15.15s | %15.15s | %15.15s |", 212 "Benchmark name", "Time/iter", "Best", "Worst", "Total", "Iterations"); 213 auto sep = "-".replicate(header.length); 214 writeln(sep); 215 writeln(header); 216 writeln(sep); 217 foreach(f; Functions){ 218 auto bs = benchmark(&f); 219 bs.name = __traits(identifier, f); 220 br.samples ~= bs; 221 string str_per_iter= format("%s (+/- %s)", 222 nsecsToStr(cast(ulong)bs.ns_iter_summ.median), 223 nsecsToStr(cast(ulong)(bs.ns_iter_summ.max - bs.ns_iter_summ.min))); 224 writef("%25.25s | %30.30s", bs.name, str_per_iter); 225 writef(" | %15.15s | %15.15s", 226 nsecsToStr(cast(ulong)bs.ns_iter_summ.min), 227 nsecsToStr(cast(ulong)bs.ns_iter_summ.max)); 228 writef(" | %15.15s | %15.15s |\n", 229 nsecsToStr(cast(ulong)bs.total_ns), 230 format("%s",bs.total_iterations)); 231 } 232 writeln(sep); 233 234 return br; 235 } 236 237 unittest 238 { 239 static void f1(ref Bencher bencher){ 240 import std.algorithm.iteration: sum; 241 import std.array; 242 243 bencher.iter((){ 244 return [1e20, 1.5, -1e20].sum(); 245 }); 246 } 247 248 static void f2(ref Bencher bencher){ 249 import std.algorithm.iteration: sum, map; 250 import std.range: iota; 251 import std.array; 252 immutable nums = [-1e30, 1e60, 1e30, 1.0, -1e60]; 253 auto v = iota(0, 500).map!(i => nums[i % 5]).array; 254 bencher.iter((){ 255 return v.sum(); 256 }); 257 } 258 259 benchmark(&f1); 260 benchmark(&f2); 261 BenchMain!(f1, f2); 262 263 // black_box(benchmark(&f1)); 264 // black_box(benchmark(&f2)); 265 // black_box(BenchMain!(f1, f2)); 266 }