Fastest JSON parser in the world is a D project?
forum.dlang.org
forum.dlang.org
https://github.com/kostya/benchmarks/pull/46#issuecomment-14...
[1] This is a board generalization not necessarily true for all SIMD opcodes.
Something like this:
d := _mm_loadu_ps <json>
b := _mm_loadu_ps <token: '{'>
n := _mm_cmpistrc(d, b)
You'd have to be clever to skip around strings ("... \"sonuva ... "), but once that's handled, you'd have significant speed ups to scan for ',', '{', '}', etc.I think the double-quote escape might look something like this:
d := _mm_loadu_ps <json>
q := _mm_loadu_ps <token: '"'>
e := _mm_loadu_ps <token: '\\'>
n := _mm_cmpistrc(d, q)
m := _mm_cmpistrc(d, e)
if m+1 == n:
branch-to-top
... process ...
Looks like cmpistrc has 1/2 reciprocal throughput. If you unrolled the loop 8 deep, you're probably looking at 10c per 16bytes scanned. {"coordinates": [
{"x": 0.65, "y": 0.23, "z": 0.91, "name": "fwgzd", "opts": {'1': [1, true]},
{"x": 0.45, "y": 0.78, "z": 0.22, "name": "alfsj", "opts": {'1': [1, true]},
...
],
"info": "some info"}
The benchmark code [1] (very readable) is reading an array of structs containing x,y,z from 'coordinates'.[1] https://github.com/kostya/benchmarks/blob/master/json/test_f...
I haven't read the code but the algorithm would look at this roughly like:
See `{` process? yes
See `"coordinates"` process? yes
See `[` process? yes
See `{` process? yes
See `"x"` process? yes => new Coord, Coord.x = 0.65
See `"y"` process? yes => Coord.y = 0.23
See `"z"` process? yes => Coord.z = 0.91
See `"name"` process? no, next token is `"`, skip to `"`
See `"opts"` process? no, next token is `{`, skip to `}`
The tradeoff is that he's completely ignoring the contents of `name` or `opts` or `info` and those values could potentially be invalid JSON but this processor doesn't care.The code is also picking up efficiencies from being a static C-like language. The "new Coord" isn't actually doing anything, the alloc happened for the array as a whole so the assignments just write a known size value to a known offset from the start of the array. He's also using SIMD instructions to process multiple bytes at a time and some other tricks but the skipping is the main difference.
I think it's also interesting that the Rust code implemented value skipping in the benchmark file itself. The relative slowness there is likely that the library used (serde_json) is the JSON plugin for a generic serialization/deserialization lib and that Rust doesn't have a way to do SIMD yet.
Ohttps://github.com/kostya/benchmarks/pull/44
I'm guessing gcc just has some optimizations llvm doesn't.
Rust does have some experimental SIMD, but I'm not using it yet because I want the serde libraries to be safe to use on byte streams, and reading 16 bytes ahead could block if at the end of a socket stream. Hopefully we will get specialization soon, which would let me use SIMD when I know I have at least X bytes in a buffer.
One thing I noticed in this example was that the D example worked pretty much exactly like I want serde to work in that it was able to deserialize a subset of the overall document and the Coord struct didn't need to exhaustively cover the individual json data objects. If there's a way to do this in serde, an example in the docs would be really helpful.
If you tell it do deserialize to a `Point { x: u32, y: u32 }`, it will ignore any additional fields.
I just pushed up a rust pull parser version here: https://github.com/kostya/benchmarks/pull/54. Is that what you were thinking of?
I see you have a reply to Gankro for a non-exhaustive flag and that'd work. As for the default, the current behavior is what I'd expect from a Rust lib given the correctness first mindset of the community but I will always be opting for non-exhaustive because I think most people providing JSON APIs consider additional keys to be backwards compatible (they are in dynamic languages) and I'd prefer my apps to not break in production for no apparent reason.
(Why so fast)
"On the downside I did not validate the unused side-structures. I think it is not necessary to validate data you are not using. So basically I only scan them so much as to find where they end. Granted it is a bit of optimization for a benchmark, but is actually handy in real-life as well."
Anyone using D in real life, among hackers here?
edit: They have series of blog posts documenting the same too - https://www.sociomantic.com/search/tag/dlang (6 so far).
[1]: http://www.drdobbs.com/mobile/facebook-adopts-d-language/240... [2]: https://github.com/facebook/warp [3]: https://github.com/facebook/flint
The standard library forking issue is far in the past now - which doesn't mean that it's as complete and comprehensive as it could be yet - for an example of one I ran into the other day, the "pure" annotation is absent from some math functions because they call out to C standard libraries which use global state for error codes.
But the things that are the focus now are mainly "nice-to-have" technologies that will be good for productivity - check out "std.experimental.allocator" for an idea of what's cooking. There are also multiple implementations of the compiler tech rolling around now, not just the Digital Mars one, which is a good sign for future quality.
Using it for small personal projects. Happy. :)
I couldn't dream of writing all of the features I now have in another systems language because of all the extra scaffolding it would require, and I couldn't hope to get anywhere near the performance I have in higher-level languages because I'd lose value types and manual memory management.
The multiple stdlib problem has been solved for years now.
Everything else works: calling C functions, ctags, syntax highlighting, automatic make dependency generation, integration in Visual Studio, step by step debugging, profiling (oprofile), valgrind, etc. As a bonus, D makes a fantastic "scripting" language (= no explicit compilation step): at work, we're progressively replacing all of our bash scripts with D scripts.
I'm in the hedge fund world, and my day job is investing but I use technology to help me do that. Andy Smith gave a talk on using D at a 20bn+ hedge fund at dconf. My background is at similar large funds, but I am now using D to develop some tools to help the investment process at a smaller but decent sized fund. A couple of D people will be helping me. So it's ready for real work, and the combination of high productivity with efficiency and correctness is a killer feature for my problem set. Fast compilation is also important as its a dynamic environment and you want to iterate quickly.
* For the dynamic languages execution time includes the time it takes to lex, parse and interpret the source code.
* For language implementations with a JIT execution time includes the time the JIT takes to properly optimise hot code paths. Generally you start benchmarking after a warm up period in such cases.
The only fair comparisons are those between ahead of time compiled languages.
Why? STDLIB parser loads the file line-by-line, then copies line by line into a new buffer that joins the strings together. Then the parser is called. The Scala community normally circumvents by using non-standard community developed solutions.
Furthermore JIT vs Compiled language benchmarks are pretty unfair to JIT'd languages. Especially for the JVM which doesn't start to compile sections until >10,000 calls.
[1] https://groups.google.com/forum/m/#!msg/scala-user/P7-8PEUUj...
[0] https://github.com/non/jawn [1] https://github.com/spray/spray-json
Nice to see that it only took a couple of years for the D community to beat in speed the scripting languages.
"A few days ago I decided to get some practical use out of my pet project 'fast' by implementing a JSON parser myself, that could rival even the by then fastest JSON parser, RapidJSON. The result can be seen in the benchmark results right now:
https://github.com/kostya/benchmarks#json
fast: 0.34s, 226.7Mb (GDC) RapidJSON: 0.79s, 687.1Mb (GCC)"
You were probably thinking of something like the "dynamic" type that C# has.
In C auto just defines the storage location (and is kinda useless), not the type (which you still need to define).
auto x = 3;
"Infers" 'int'. Everything else is an error. auto x = 15;
if (some_condition) {
x = "Hello world!";
}
print(x);
And this solves real errors. I believe there's even a function somewhere in the Python standard library that, if it finds one result, returns a string, and if it finds multiple results, returns a list of strings. Of course a string is itself iterable, so duck-typing goes horribly wrong. > (1 + "1") * 2
22
At least it raises a TypeError in Python (unsupported operand type(s) for +: 'int' and 'str'). OK, probably better to fail at compile time, but I would rather fail at runtime than not fail at all.Besides the concatenation of numbers to strings and vice versa, most other type errors in JavaScript will give you a syntax error or something like NaN (Not a Number).
So don't mix strings and numbers. Everything else is just "objects". =)
One thing that causes lots of bugs though is undefined object properties. But I'm not sure if inferred types would help in those cases.
'dynamic' is the key word here. The title is misleading.
I wouldn't be surprised if most of the speed difference was down to correctness checks though.
That's setup for the reveal of "fast" which not only does better than dynamic language stdlibs and than the previous fastest D library but does better than any other JSON parser.
The point of the historical recap is how fast things improved for the D ecosystem: in 7 months the best-case option went from parsing JSON 2~3 times slower than Ruby to parsing it in half the time of RapidJSON, a 2 orders of magnitude improvement in speed.
Benchmarks are for the poor people who can't live in the real world.
Anyone else is free to do the same.
If you know of a faster parser in any language, I would love to hear, because getting the job done well matters more to me.