1 module unit_threaded.randomized.benchmark; 2 3 import unit_threaded.from; 4 5 /* This function used $(D MonoTimeImpl!(ClockType.precise).currTime) to time 6 how long $(D MonoTimeImpl!(ClockType.precise).currTime) takes to return 7 the current time. 8 */ 9 private auto medianStopWatchTime() 10 { 11 import core.time; 12 import std.algorithm : sort; 13 import std.datetime: Duration, MonoTimeImpl; 14 15 enum numRounds = 51; 16 Duration[numRounds] times; 17 18 MonoTimeImpl!(ClockType.precise) dummy; 19 for (size_t i = 0; i < numRounds; ++i) 20 { 21 auto sw = MonoTimeImpl!(ClockType.precise).currTime; 22 dummy = MonoTimeImpl!(ClockType.precise).currTime; 23 dummy = MonoTimeImpl!(ClockType.precise).currTime; 24 doNotOptimizeAway(dummy); 25 times[i] = MonoTimeImpl!(ClockType.precise).currTime - sw; 26 } 27 28 sort(times[]); 29 30 return times[$ / 2].total!"hnsecs"; 31 } 32 33 private from!"std.datetime".Duration getQuantilTick 34 (double q) 35 (from!"std.datetime".Duration[] ticks) 36 pure @safe 37 { 38 size_t idx = cast(size_t)(ticks.length * q); 39 40 if (ticks.length % 2 == 1) 41 { 42 return ticks[idx]; 43 } 44 else 45 { 46 return (ticks[idx] + ticks[idx - 1]) / 2; 47 } 48 } 49 50 // @Name("Quantil calculations") 51 // unittest 52 // { 53 // static import std.conv; 54 // import std.algorithm.iteration : map; 55 56 // auto ticks = [1, 2, 3, 4, 5].map!(a => dur!"seconds"(a)).array; 57 58 // Duration q25 = getQuantilTick!0.25(ticks); 59 // assert(q25 == dur!"seconds"(2), q25.toString()); 60 61 // Duration q50 = getQuantilTick!0.50(ticks); 62 // assert(q50 == dur!"seconds"(3), q25.toString()); 63 64 // Duration q75 = getQuantilTick!0.75(ticks); 65 // assert(q75 == dur!"seconds"(4), q25.toString()); 66 67 // q25 = getQuantilTick!0.25(ticks[0 .. 4]); 68 // assert(q25 == dur!"seconds"(1) + dur!"msecs"(500), q25.toString()); 69 70 // q50 = getQuantilTick!0.50(ticks[0 .. 4]); 71 // assert(q50 == dur!"seconds"(2) + dur!"msecs"(500), q25.toString()); 72 73 // q75 = getQuantilTick!0.75(ticks[0 .. 4]); 74 // assert(q75 == dur!"seconds"(3) + dur!"msecs"(500), q25.toString()); 75 // } 76 77 /** The options controlling the behaviour of benchmark. */ 78 struct BenchmarkOptions 79 { 80 import std.datetime: Duration, seconds; 81 82 string funcname; // the name of the function to benchmark 83 string filename; // the name of the file the results will be appended to 84 Duration duration = 1.seconds; // the time after which the function to 85 // benchmark is not executed anymore 86 size_t maxRounds = 10000; // the maximum number of times the function 87 // to benchmark is called 88 int seed = 1337; // the seed to the random number generator 89 90 this(string funcname) 91 { 92 this.funcname = funcname; 93 } 94 } 95 96 /** This $(D struct) takes care of the time taking and outputting of the 97 statistics. 98 */ 99 struct Benchmark 100 { 101 import std.array : Appender; 102 import std.datetime: Duration, MonoTimeImpl, ClockType; 103 104 string filename; // where to write the benchmark result to 105 string funcname; // the name of the benchmark 106 size_t rounds; // the number of times the functions is supposed to be 107 //executed 108 string timeScale; // the unit the benchmark is measuring in 109 real medianStopWatch; // the median time it takes to get the clocktime twice 110 bool dontWrite; // if set, no data is written to the the file name "filename" 111 // true if, RndValueGen opApply was interrupt unexpectitally 112 Appender!(Duration[]) ticks; // the stopped times, there will be rounds ticks 113 size_t ticksIndex = 0; // the index into ticks 114 size_t curRound = 0; // the number of rounds run 115 MonoTimeImpl!(ClockType.precise) startTime; 116 Duration timeSpend; // overall time spend running the benchmark function 117 118 /** The constructor for the $(D Benchmark). 119 Params: 120 funcname = The name of the $(D benchmark) instance. The $(D funcname) 121 will be used to associate the results with the function 122 founds = How many rounds. 123 filename = The $(D filename) will be used as a filename to store the 124 results. 125 */ 126 this(in string funcname, in size_t rounds, in string filename) 127 { 128 import std.array : appender; 129 this.filename = filename; 130 this.funcname = funcname; 131 this.rounds = rounds; 132 this.timeScale = "hnsecs"; 133 this.ticks = appender!(Duration[])(); 134 this.medianStopWatch = medianStopWatchTime(); 135 } 136 137 /** A call to this method will start the time taking process */ 138 void start() 139 { 140 this.startTime = MonoTimeImpl!(ClockType.precise).currTime; 141 } 142 143 /** A call to this method will stop the time taking process, and 144 appends the execution time to the $(D ticks) member. 145 */ 146 void stop() 147 { 148 auto end = MonoTimeImpl!(ClockType.precise).currTime; 149 Duration dur = end - this.startTime; 150 this.timeSpend += dur; 151 this.ticks.put(dur); 152 ++this.curRound; 153 } 154 155 ~this() 156 { 157 import std.stdio : File; 158 import std.datetime: Clock; 159 160 if (!this.dontWrite && this.ticks.data.length) 161 { 162 import std.algorithm : sort; 163 164 auto sortedTicks = this.ticks.data; 165 sortedTicks.sort(); 166 167 auto f = File(filename ~ "_bechmark.csv", "a"); 168 scope (exit) 169 f.close(); 170 171 auto q0 = sortedTicks[0].total!("hnsecs")() / 172 cast(double) this.rounds; 173 auto q25 = getQuantilTick!0.25(sortedTicks).total!("hnsecs")() / 174 cast(double) this.rounds; 175 auto q50 = getQuantilTick!0.50(sortedTicks).total!("hnsecs")() / 176 cast(double) this.rounds; 177 auto q75 = getQuantilTick!0.75(sortedTicks).total!("hnsecs")() / 178 cast(double) this.rounds; 179 auto q100 = sortedTicks[$ - 1].total!("hnsecs")() / 180 cast(double) this.rounds; 181 182 // funcname, the data when the benchmark was created, unit of time, 183 // rounds, medianStopWatch, low, 0.25 quantil, median, 184 // 0.75 quantil, high 185 f.writefln( 186 "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"" 187 ~ ",\"%s\"", 188 this.funcname, Clock.currTime.toISOExtString(), 189 this.timeScale, this.curRound, this.medianStopWatch, 190 q0 > this.medianStopWatch ? q0 - this.medianStopWatch : 0, 191 q25 > this.medianStopWatch ? q25 - this.medianStopWatch : 0, 192 q50 > this.medianStopWatch ? q50 - this.medianStopWatch : 0, 193 q75 > this.medianStopWatch ? q75 - this.medianStopWatch : 0, 194 q100 > this.medianStopWatch ? q100 - this.medianStopWatch : 0); 195 } 196 } 197 } 198 199 void doNotOptimizeAway(T...)(ref T t) 200 { 201 foreach (ref it; t) 202 { 203 doNotOptimizeAwayImpl(&it); 204 } 205 } 206 207 private void doNotOptimizeAwayImpl(void* p) { 208 import core.thread : getpid; 209 import std.stdio : writeln; 210 if(getpid() == 1) { 211 writeln(*cast(char*)p); 212 } 213 } 214 215 // unittest 216 // { 217 // static void funToBenchmark(int a, float b, Gen!(int, -5, 5) c, string d, 218 // GenASCIIString!(1, 10) e) 219 // { 220 // import core.thread; 221 222 // Thread.sleep(1.seconds / 100000); 223 // doNotOptimizeAway(a, b, c, d, e); 224 // } 225 226 // benchmark!funToBenchmark(); 227 // benchmark!funToBenchmark("Another Name"); 228 // benchmark!funToBenchmark("Another Name", 2.seconds); 229 // benchmark!funToBenchmark(2.seconds); 230 // } 231 232 /** This function runs the passed callable $(D T) for the duration of 233 $(D maxRuntime). It will count how often $(D T) is run in the duration and 234 how long each run took to complete. 235 236 Unless compiled in release mode, statistics will be printed to $(D stderr). 237 If compiled in release mode the statistics are appended to a file called 238 $(D name). 239 240 Params: 241 opts = A $(D BenchmarkOptions) instance that encompasses all possible 242 parameters of benchmark. 243 name = The name of the benchmark. The name is also used as filename to 244 save the benchmark results. 245 maxRuntime = The maximum time the benchmark is executed. The last run will 246 not be interrupted. 247 rndSeed = The seed to the random number generator used to populate the 248 parameter passed to the function to benchmark. 249 rounds = The maximum number of times the callable $(D T) is called. 250 */ 251 void benchmark(alias T)(const ref BenchmarkOptions opts) 252 { 253 import std.random : Random; 254 import std.traits: ParameterIdentifierTuple, Parameters; 255 import unit_threaded.randomized.random; 256 257 auto bench = Benchmark(opts.funcname, opts.maxRounds, opts.filename); 258 auto rnd = Random(opts.seed); 259 enum string[] parameterNames = [ParameterIdentifierTuple!T]; 260 auto valueGenerator = RndValueGen!(parameterNames, Parameters!T)(&rnd); 261 262 while (bench.timeSpend <= opts.duration && bench.curRound < opts.maxRounds) 263 { 264 valueGenerator.genValues(); 265 266 bench.start(); 267 try 268 { 269 T(valueGenerator.values); 270 } 271 catch (Throwable t) 272 { 273 import std.experimental.logger : logf; 274 275 logf("unittest with name %s failed when parameter %s where passed", 276 opts.funcname, valueGenerator); 277 break; 278 } 279 finally 280 { 281 bench.stop(); 282 ++bench.curRound; 283 } 284 } 285 } 286 287 /// Ditto 288 void benchmark(alias T)(string funcname = "", string filename = __FILE__) 289 { 290 import std..string : empty; 291 import std.traits: fullyQualifiedName; 292 293 auto opt = BenchmarkOptions( 294 funcname.empty ? fullyQualifiedName!T : funcname 295 ); 296 opt.filename = filename; 297 benchmark!(T)(opt); 298 } 299 300 /// Ditto 301 void benchmark(alias T)(from!"std.datetime".Duration maxRuntime, string filename = __FILE__) 302 { 303 import std.traits: fullyQualifiedName; 304 auto opt = BenchmarkOptions(fullyQualifiedName!T); 305 opt.filename = filename; 306 opt.duration = maxRuntime; 307 benchmark!(T)(opt); 308 } 309 310 /// Ditto 311 /*void benchmark(alias T)(string name, string filename = __FILE__) 312 { 313 auto opt = BenchmarkOptions(name); 314 opt.filename = filename; 315 benchmark!(T)(opt); 316 }*/ 317 318 /// Ditto 319 void benchmark(alias T)(string name, from!"std.datetime".Duration maxRuntime, 320 string filename = __FILE__) 321 { 322 auto opt = BenchmarkOptions(name); 323 opt.filename = filename; 324 opt.duration = maxRuntime; 325 benchmark!(T)(opt); 326 }