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 }