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 }