1 /***************************************************************************************************
2  * 
3  * Internal benchmark module.
4  * 
5  * Authors:
6  *   $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise)
7  * 
8  * Copyright:
9  *   © 2015 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise)
10  * 
11  * License:
12  *   $(LINK2 http://www.gnu.org/licenses/gpl-3.0, GNU General Public License 3.0)
13  * 
14  **************************************************************************************************/
15 module fast.benchmarks;
16 
17 version (benchmark):
18 
19 void main()
20 {
21 	import std.stdio;
22 	import core.stdc.string, core.stdc.stddef, core.stdc.stdlib;
23 	import std.array, std.stdio, std.algorithm, std.regex, std.utf, std.conv, std.string, std.range;
24 	import fast.string, fast.cstring, fast.buffer, fast.format, fast.json;
25 	import std.format : formattedWrite;
26 
27 	static immutable nums = { ulong[1uL << 8] nums = void; foreach (i; 0 .. nums.length) nums[i] = (1uL << (64 - 8)) * i; return nums; }();
28 	static immutable part1 = "C:\\";
29 	static immutable part2 = "Documents and Settings\\User\\My Documents\\My Downloads\\";
30 	static immutable part3 = "Fast.zip";
31 	static immutable pathname = "hello/i_am_a/path_name\\with_several_different\\slashes";
32 	static immutable zeroterm = "wefwfnqwefnw(eknwoemkf)moorroijqwoijq&oqo(vqwojkpjavnal(nvo(eirvn$wefwfnqwefnw(eknwoemkf)moorroijqwoihqioqo(vqwojkpjavnal(nvo(eirvn$wefwfnqwef\"w(eknwoemkf)moorroijqwoijqioqo(vqwojkpjavnal(nvo(eirvn$\0";
33 	static pathSepRegex = ctRegex!`[/\\]`;
34 	enum pathnameWStringLength = to!(immutable(wchar_t)[])(pathname).length;
35 		run ("Format strings for integers...", 13093,
36 			benchmark ("std.*.format", () { uint check; foreach (ulong num; nums) { string str = format("decimal: %s, hex: %x", num, num); check += str[9]; } return check; } ),
37 			benchmark ("fast.*.format", () { uint check; foreach (ulong num; nums) { string str = format!"decimal: %s, hex: %x"(num, num); check += str[9]; } return check; } ),
38 			benchmark ("fast.*.formata", () { uint check; foreach (ulong num; nums) { char[] str = formata!"decimal: %s, hex: %x"(num, num); check += str[9]; } return check; } ),
39 			);
40 
41 		run ("Convert 256 numbers to fixed width hex strings...", 0x20,
42 			benchmark ("std.*.formattedWrite", () { Appender!(char[]) app; app.reserve(16); char check = 0; foreach (ulong num; nums) { app.formattedWrite("%016X", num); check += app.data[0]; app.clear(); } return check; }),
43 			benchmark ("fast.*.hexStrUpper", () { char[16] str; char check = 0; foreach (ulong num; nums) { str = hexStrUpper(num); check += str[0]; } return check; }),
44 			);
45 
46 		run ("Concatenate a known number of strings...", part1.length + part2.length + part3.length,
47 			benchmark ("std.array.appender", () { auto app = appender(part1); app ~= part2; app ~= part3; return app.data.length; }),
48 			benchmark ("~", () { string path = part1 ~ part2 ~ part3; return path.length; }),
49 			benchmark ("fast.string.concat", () { size_t length; { auto path = concat!(part1, part2, part3); length = path.length; } return length; }),
50 			);
51 
52 		run ("Allocate a temporary char buffer and fill it with 0xFF...", '\xFF',
53 		     benchmark ("new", () { auto str = new char[](zeroterm.length); return str[$-1]; }),
54 		     benchmark ("malloc", () { auto ptr = cast(char*) malloc(zeroterm.length); scope(exit) free(ptr); memset(ptr, 0xFF, zeroterm.length); return ptr[zeroterm.length-1]; }),
55 		     benchmark ("fast.buffer.tempBuffer", () { char result; { auto buf = tempBuffer!(char, zeroterm.length); memset(buf, 0xFF, zeroterm.length); result = buf[$-1]; } return result; }),
56 			);
57 
58 		run("Convert a string to a wchar*...", wchar('\0'),
59 		    benchmark ("toUTFz", () { return toUTFz!(wchar*)(pathname)[pathnameWStringLength]; }),
60 			benchmark ("cstring.wcharPtr", () { wchar result; { auto buf = wcharPtr!pathname; result = buf.ptr[pathnameWStringLength]; } return result; }),
61 			);
62 
63 		run("Convert a string to a char*...", '\0',
64 		    benchmark ("toUTFz", () { return toUTFz!(char*)(pathname)[pathname.length]; }),
65 		    benchmark ("toStringz", () { return cast(char) toStringz(pathname)[pathname.length]; }),
66 		    benchmark ("cstring.charPtr", () { return cast(char) charPtr!pathname[pathname.length]; }),
67 			);
68 
69 		run ("Split a string at each occurance of <, >, & and \"...", "w(eknwoemkf)moorroijqwoijqioqo(vqwojkpjavnal(nvo(eirvn$\0",
70 		     benchmark (`while+if with 4 cond.`, () { string before; immutable(char*) stop = zeroterm.ptr + zeroterm.length; immutable(char)* iter = zeroterm.ptr; immutable(char)* done = zeroterm.ptr; if (iter !is stop) do { char c = *iter++; if (c == '<' || c == '>' || c == '&' || c == '"') { before = done[0 .. iter - done]; done = iter; }} while (iter !is stop); return done[0 .. stop - done]; }),
71 		     benchmark ("fast.string.split", () { string before, after = zeroterm; while (fast..string.split!`or(or(=<,=>),or(=&,="))`(after, before, after)) {} return before; }),
72 			);
73 
74 		run ("Split a path by '/' or '\\'...", "slashes",
75 		     benchmark ("std.regex.split", () { return split(pathname, pathSepRegex)[$-1]; }),
76 		     benchmark ("std.regex.splitter", () { string last; auto range = splitter(pathname, pathSepRegex); while (!range.empty) { last = range.front; range.popFront(); } return last; }),
77 		     benchmark ("fast.string.split", () { string before, after = pathname; while (fast..string.split!`or(=\,=/)`(after, before, after)) {} return before; }),
78 			);
79 
80 	jsonCoordinates();
81 
82 	writeln("Benchmark done!");
83 }
84 
85 
86 
87 private:
88 
89 void jsonCoordinates()
90 {
91 	// A variant of https://github.com/kostya/benchmarks with less coordinate tuples,
92 	// since we repeat the test runs until a time span of one second passed.
93 	import core.memory;
94 	import std.algorithm;
95 	import std.ascii;
96 	import std.format;
97 	import std.random;
98 	import std.range;
99 	import std.typecons;
100 	import fast.helpers;
101 
102 	enum coordCount = 10_000;
103 	auto rng = Mt19937(0);
104 	__gshared string text = "{\n  \"coordinates\": [\n";
105 	foreach (i; 0 .. coordCount)
106 	{
107 		text ~= format("    {\n      \"x\": %.17g,\n      \"y\": %.17g,\n      \"z\": %.17g,\n" ~
108 			"      \"name\": \"%s %s\",\n      \"opts\": {\n        \"1\": [\n          1,\n          true\n" ~
109 			"        ]\n      }\n    }", uniform(0.0, 1.0, rng), uniform(0.0, 1.0, rng), uniform(0.0, 1.0, rng),
110 			iota(5).map!(_ => lowercase[uniform(0, $, rng)]), uniform(0, 10000, rng));
111 		text ~= (i == coordCount - 1) ? "\n" : ",\n";
112 	}
113 	text ~= "  ],\n  \"info\": \"some info\"\n}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
114 	text = text[0 .. $-16];
115 	
116 	GC.collect();
117 
118 	// Dlang on x86 with optimizations rounds up double additions.
119 	static if (isX86 && isRelease)
120 		enum expect = tuple(0.49823454184104704, 0.50283215330409059, 0.49828840592580270);
121 	else
122 		enum expect = tuple(0.49683911677479053, 0.50166077554665356, 0.49647639699603635);
123 
124 	run!(1, coordCount)("JSON 3D coordinates", expect,
125 		benchmark("std.json", {
126 				import std.json;
127 				
128 				auto json = parseJSON(text);
129 				auto coordinates = json["coordinates"].array;
130 				size_t len = coordinates.length;
131 				double x = 0, y = 0, z = 0;
132 				foreach (i; 0 .. len)
133 				{
134 					auto coord = coordinates[i];
135 					x += coord["x"].floating;
136 					y += coord["y"].floating;
137 					z += coord["z"].floating;
138 				}
139 				
140 				return tuple(x / len, y / len, z / len);
141 			}),
142 //			benchmark("stdx.data.json", {
143 //					import stdx.data.json.lexer;
144 //					import stdx.data.json.parser;
145 //
146 //					auto json = parseJSONStream!(LexOptions.useBigInt)(text);
147 //					json.skipToKey("coordinates");
148 //					size_t len;
149 //					double x = 0, y = 0, z = 0;
150 //					json.readArray(delegate() @trusted {
151 //							json.readObject!(typeof(json))(delegate(string key) @trusted {
152 //									if (key == "x")
153 //										x += json.readDouble();
154 //									else if (key == "y")
155 //										y += json.readDouble();
156 //									else if (key == "z")
157 //										z += json.readDouble();
158 //									else
159 //										json.skipValue();
160 //								});
161 //							len++;
162 //						});
163 //
164 //					return tuple(x / len, y / len, z / len);
165 //				}),
166 		benchmark("fast.json", {
167 				import fast.json;
168 
169 				auto json = Json!(validateAll, true)(text);
170 				size_t len;
171 				double x = 0, y = 0, z = 0;
172 				foreach (i; json.coordinates)
173 				{
174 					json.keySwitch!("x", "y", "z")(
175 						{ x += json.read!double; },
176 						{ y += json.read!double; },
177 						{ z += json.read!double; }
178 					);
179 					len++;
180 				}
181 
182 				return tuple(x / len, y / len, z / len);
183 			}),
184 		);
185 }
186 
187 
188 /*******************************************************************************
189  * 
190  * Runs a set of `Benchmark`s and prints comparing runtime statistics. The
191  * functions are always called until at least a second of time has passed.
192  *
193  * Params:
194  *   innerLoop = how many iterations to perform without looking at the clock
195  *   mul = typically `1`, unless the called functions repeat an action multiple
196  *         times and you want to see that reflected in the output
197  *   title = short overall title of this comparing benchmark
198  *   expectation = return value, that is expected from all the tested functions
199  *                 for validation purposes and to counter dead-code elimination.
200  *   benchmarks = A set of `Benchmark`s to be run and compared. The first one in
201  *                the list acts as a reference timing for the others.
202  *
203  **************************************/
204 void run(uint innerLoop = 1000, uint mul = 1, R)(in string title, in R expectation, in Benchmark!R[] benchmarks...)
205 {
206 	import core.time, std.stdio, std.exception, std.string;
207 	
208 	writeln("\x1b[1m", title, "\x1b[0m");
209 	writeln();
210 	ulong reference;
211 	foreach (i, ref bm; benchmarks) {
212 		// Check that the result is as expected...
213 		auto actual = bm.run();
214 		enforce(actual == expectation, format(`Benchmark "%s" did not result as expected in "%s", but in "%s".`,
215 				bm.title, expectation, actual));
216 		ulong iters = 0;
217 		immutable t1 = TickDuration.currSystemTick;
218 		TickDuration t2;
219 		do {
220 			foreach (k; 0 .. innerLoop)
221 				bm.run();
222 			iters++;
223 			t2 = TickDuration.currSystemTick;
224 		} while (!(t2 - t1).seconds);
225 		ulong times = iters * innerLoop * mul * 1_000_000_000 / (t2 - t1).nsecs;
226 		if (i == 0) {
227 			reference = times;
228 			writefln("  %-22s: %10s per second", bm.title, times);
229 		} else if (reference <= times) {
230 			writefln("\x1b[1m  %-22s: %10s per second (done in %.0f%% of time !)\x1b[0m", bm.title, times, 100.0 * reference / times);
231 		} else {
232 			writefln("  %-22s: %10s per second (slower by factor %.1f)", bm.title, times, 1.0 * reference / times);
233 		}
234 	}
235 	writeln();
236 }
237 
238 
239 /*******************************************************************************
240  * 
241  * Functor to create `Benchmark` structs.
242  *
243  * Params:
244  *   title = displayed string when the statistics of `run` are displayed
245  *   run   = the benchmarked function
246  *
247  * Returns:
248  *   a `Benchmark` from the given information
249  *
250  **************************************/
251 Benchmark!R benchmark(R)(string title, R function() run)
252 {
253 	return Benchmark!R(title, run);
254 }
255 
256 
257 /*******************************************************************************
258  * 
259  * Information about a benchmarked function.
260  *
261  **************************************/
262 struct Benchmark(R)
263 {
264 	string title;
265 	R function() run;
266 }