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 }