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