1 module mutils.benchmark; 2 3 import std.algorithm : sort; 4 import std.conv : to; 5 import std.file; 6 import std.format : format; 7 import std.stdio; 8 9 import mutils.container.buckets_chain; 10 import mutils.container.vector; 11 import mutils.time; 12 13 enum doNotInline = "version(DigitalMars)pragma(inline,false);version(LDC)pragma(LDC_never_inline);"; 14 void doNotOptimize(Args...)(ref Args args) { 15 asm { 16 naked; 17 ret; 18 } 19 } // function call overhead 20 21 struct BenchmarkData(uint testsNum, uint iterationsNum) { 22 long[iterationsNum][testsNum] times; 23 24 void setTime(size_t testNum)(size_t iterationNum, size_t time) { 25 times[testNum][iterationNum] = time; 26 } 27 28 void start(size_t testNum)(size_t iterationNum) { 29 times[testNum][iterationNum] = useconds(); 30 } 31 32 void end(size_t testNum)(size_t iterationNum) { 33 long end = useconds(); 34 times[testNum][iterationNum] = end - times[testNum][iterationNum]; 35 } 36 37 void writeToCsvFile()(string outputFileName) { 38 writeToCsvFile(outputFileName, defaultTestNames[0 .. testsNum]); 39 } 40 41 void writeToCsvFile(string outputFileName, string[testsNum] testNames) { 42 float to_ms = 1000.0 / 1_000_000; 43 auto f = File(outputFileName, "w"); 44 scope (exit) 45 f.close(); 46 47 f.write("index,"); 48 foreach (name; testNames) { 49 f.write(name); 50 f.write(" [ms],"); 51 } 52 f.write("\n"); 53 foreach (i; 0 .. iterationsNum) { 54 f.write(i); 55 f.write(","); 56 foreach (j; 0 .. testsNum) { 57 f.write(times[j][i] * to_ms); 58 f.write(","); 59 } 60 f.write("\n"); 61 } 62 } 63 64 void plotUsingGnuplot(string outputFileName) { 65 plotUsingGnuplot(outputFileName, defaultTestNames[0 .. testsNum]); 66 } 67 68 void plotUsingGnuplot(string outputFileName, string[testsNum] testNames) { 69 70 string temDir = tempDir(); 71 string tmpOutputFileCsv = temDir ~ "/tmp_bench.csv"; 72 writeToCsvFile(tmpOutputFileCsv, testNames); 73 scope (exit) 74 remove(tmpOutputFileCsv); 75 76 string tmpScript = temDir ~ "/tmp_bench.gnuplot"; 77 auto f = File(tmpScript, "w"); 78 scope (exit) 79 remove(tmpScript); 80 f.write(gnuplotScriptParts[0]); 81 f.write(outputFileName); 82 f.write(gnuplotScriptParts[1]); 83 f.write(tmpOutputFileCsv); 84 f.write(gnuplotScriptParts[2]); 85 f.close(); 86 87 import std.process; 88 89 auto result = execute(["gnuplot", tmpScript]); 90 assert(result.status == 0); 91 } 92 93 string[3] gnuplotScriptParts = [` 94 # 95 set terminal png 96 set output '`, `' 97 set key autotitle columnhead 98 99 set key left box 100 set samples 50 101 set style data points 102 103 set datafile separator "," 104 #plot using 1:2 with linespoints, 'result.csv' using 1:3 with linespoints 105 plot for [col=2:40] '`, 106 `' using 1:col with linespoints`]; 107 108 static string[10] defaultTestNames = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10",]; 109 110 } 111 112 unittest { 113 import std.meta; 114 115 static auto mul(T)(T a) { 116 mixin(doNotInline); 117 return a + 200 * a; 118 } 119 120 alias BenchTypes = AliasSeq!(int, double); 121 enum iterationsNum = 40; 122 123 BenchmarkData!(BenchTypes.length, iterationsNum) bench; 124 125 foreach (testNum, TYPE; BenchTypes) { 126 foreach (itNum; 0 .. iterationsNum) { 127 bench.start!(testNum)(itNum); 128 TYPE sum = 0; 129 foreach (i; 1 .. 30_000_0) { 130 sum += mul(cast(TYPE) i); 131 } 132 doNotOptimize(sum); 133 bench.end!(testNum)(itNum); 134 } 135 } 136 //bench.writeToCsvFile("test.csv",["mul int", "mul double"]); 137 //bench.plotUsingGnuplot("test.csv",["mul int", "mul double"]); 138 139 } 140 141 static BucketsChain!(PerfData, 64, false) perfDataAlloc; 142 143 struct PerfData { 144 Vector!(PerfData*) perfs; 145 string funcName; 146 long totalTime; 147 int calls; 148 149 float totalTimeToFloat() { 150 return cast(float) totalTime / 1_000_000; 151 } 152 153 void reset() { 154 totalTime = 0; 155 calls = 0; 156 foreach (ref p; perfs) { 157 p.reset(); 158 } 159 } 160 161 PerfData* getPerfData(string funcName) { 162 foreach (ref p; perfs) { 163 if (p.funcName == funcName) { 164 return p; 165 } 166 } 167 PerfData* p = perfDataAlloc.add(); 168 p.funcName = funcName; 169 perfs ~= p; 170 return p; 171 } 172 173 void sortByName() { 174 sort!("a.funcName>b.funcName")(perfs[]); 175 foreach (p; perfs) { 176 p.sortByName(); 177 } 178 } 179 180 static int lvl = -1; 181 string toString() { 182 lvl++; 183 string str; 184 str ~= format("%s%s \n", lvl, funcName); 185 str ~= format("%s%s %s %.*s \n", lvl, totalTime, calls, cast(long)(totalTime / 10_000_000), 186 "###################################################################"); 187 foreach (p; perfs) { 188 str ~= p.toString(); 189 } 190 lvl--; 191 return str; 192 } 193 194 } 195 /// Can be used only once in function 196 /// Always use: auto timeThis=TimeThis.time(); alone TimeThis.time(); is not working 197 struct TimeThis { 198 199 static PerfData[2] timingRoot; 200 static bool timingRootIndex; 201 static PerfData* currentTiming; 202 static bool enableTiming = true; 203 204 string funcName; 205 PerfData* timingMyRoot; 206 long timeStart; 207 208 static void initializeStatic() { 209 currentTiming = &timingRoot[0]; 210 } 211 212 static void clearRoots() { 213 timingRoot[0].perfs.clear(); 214 timingRoot[1].perfs.clear(); 215 } 216 217 @disable this(); 218 this(string funcName, long time) { 219 if (!enableTiming) { 220 return; 221 } 222 this.funcName = funcName; 223 timeStart = time; 224 timingMyRoot = currentTiming; 225 currentTiming = timingMyRoot.getPerfData(funcName); 226 } 227 228 ~this() { 229 if (timeStart == 0) { 230 return; 231 } 232 long timeEnd = useconds(); 233 long dt = timeEnd - timeStart; 234 235 currentTiming.totalTime += dt; 236 currentTiming.calls += 1; 237 currentTiming = timingMyRoot; 238 } 239 240 static TimeThis time(string funcName = __FUNCTION__) { 241 return TimeThis(funcName, useconds()); 242 } 243 244 static void print() { 245 //foreach(p; timingRoot.perfs){ 246 //writeln(*p); 247 //} 248 } 249 250 static PerfData*[] getRootPerfs() { 251 return timingRoot[!timingRootIndex].perfs[]; 252 } 253 254 static void reset() { 255 if (!enableTiming) { 256 return; 257 } 258 assert(currentTiming == &timingRoot[timingRootIndex]); 259 timingRootIndex = !timingRootIndex; 260 currentTiming = &timingRoot[timingRootIndex]; 261 foreach (p; timingRoot[timingRootIndex].perfs) { 262 p.reset(); 263 } 264 } 265 266 static void enable(bool yes) { 267 enableTiming = yes; 268 } 269 270 static void sortByName() { 271 timingRoot[0].sortByName(); 272 timingRoot[1].sortByName(); 273 } 274 275 } 276 277 unittest { 278 mixin(checkVectorAllocations); 279 void testA() { 280 auto timeThis = TimeThis.time(); 281 } 282 283 void testB() { 284 auto timeThis = TimeThis.time(); 285 } 286 287 { 288 TimeThis.initializeStatic(); 289 auto timeThis = TimeThis.time(); 290 testA(); 291 testB(); 292 } 293 perfDataAlloc.clear(); 294 TimeThis.clearRoots(); 295 //TimeThis.print(); 296 }