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 }