Skip to content

Commit 1d1d3d1

Browse files
committed
Added fast.json module.
1 parent a32dc06 commit 1d1d3d1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4268
-399
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
/dub.selections.json
12
/__dummy.html
23
/docs.json
34
/docs/
45
/.dub/
56
/obj/
67
/bin/
78
/mono-d/
8-
9+
/source/fast/doc.d

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ A benchmark is included and can be run through dub, e.g.:
1111
dub --build=release --compiler=gdc
1212

1313
#### Examples
14+
##### Read JSON file with coordinates.
15+
```d
16+
struct Coord { double x, y, z; }
17+
18+
void main()
19+
{
20+
import fast.json;
21+
auto coords = json.coordinates.read!(Coord[]);
22+
}
23+
```
1424
##### SSE3 accelerated splitting around '/' and '\'
1525
```d
1626
string rest = pathname

dub.json

Lines changed: 0 additions & 20 deletions
This file was deleted.

dub.sdl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name "fast"
2+
description "A library that aims to provide the fastest possible implementation of some every day routines."
3+
homepage "http://github.com/mleise/fast"
4+
authors "Marco Leise"
5+
copyright "Copyright © 2015, Marco Leise"
6+
license "GPL-3.0"
7+
8+
excludedSourceFiles "source/fast/doc.d"
9+
10+
configuration "benchmark" {
11+
platforms "posix-dmd" "posix-x86_64-ldc" "posix-x86-gdc" "posix-x86_64-gdc"
12+
targetType "executable"
13+
versions "benchmark"
14+
}
15+
16+
configuration "library" {
17+
platforms "posix-dmd" "posix-x86_64-ldc" "posix-x86-gdc" "posix-x86_64-gdc"
18+
targetType "library"
19+
}

dub.selections.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

source/fast/benchmarks.d

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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+
}

0 commit comments

Comments
 (0)