mirror of
https://github.com/nlohmann/json.git
synced 2026-11-04 19:59:23 +00:00
docs: compare json_document with Glaze, sonic, RapidJSON and Rust parsers
Extends the comparison in "How 3.13.0 got faster" from yyjson, simdjson and Boost.JSON to eleven engines: sonic-cpp, RapidJSON and Glaze (generic DOM, lazy_json, reading into structs) in C++, sonic-rs, simd-json and serde_json in Rust. Measured on an Apple M1 Max (Apple clang 21, Rust 1.99) and an x86-64 Haswell server (GCC 13, Clang 18). Every engine must return the same values, string bytes and bit-identical number sums before it is timed. The post now answers whether json_document is the fastest parser: it is the fastest document parser at parse+dump everywhere and at parsing on Apple silicon, ties with simdjson's DOM on x86-64, and loses to simdjson On-Demand and Glaze structs when only a few fields are read. tests/benchmarks/json_view/others holds the programs (bench_all.cpp, the Rust crate with its Cargo.lock), summarize.py and the pinned versions and checksums. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
9 files changed
+1987
-117
No files matched your search
@@ -8,9 +8,10 @@ Since 2016, the README of this library has said this about speed:
|
|||||||
That sentence is still true, but the gap is a lot smaller now. Swap the 3.12.0 header for the 3.13.0 one and recompile,
|
That sentence is still true, but the gap is a lot smaller now. Swap the 3.12.0 header for the 3.13.0 one and recompile,
|
||||||
and the 182 benchmarks of the library's suite run on average 2.9× as fast, without a line of your code changing.
|
and the 182 benchmarks of the library's suite run on average 2.9× as fast, without a line of your code changing.
|
||||||
Parsing the 1.7 MB `citm_catalog.json` takes 1.75 ms instead of 7.20 ms, and `dump()` of documents made of objects
|
Parsing the 1.7 MB `citm_catalog.json` takes 1.75 ms instead of 7.20 ms, and `dump()` of documents made of objects
|
||||||
and strings is 4.7 to 6.7× as fast. The new read-only [`json_document`](../../features/json_view.md) parses the
|
and strings is 4.7 to 6.7× as fast. The new read-only [`json_document`](../../features/json_view.md) parses and
|
||||||
usual test files faster than yyjson and simdjson's DOM on Apple silicon (with a caveat for float-heavy files that we
|
writes faster than the other document parsers we measured on Apple silicon, including yyjson, simdjson and
|
||||||
explain below).
|
sonic-rs, and ties with simdjson on x86-64. It is not the fastest everywhere; [section 8](#8-build-a-different-data-structure)
|
||||||
|
says where it loses.
|
||||||
|
|
||||||
This post is about *how*. None of the techniques is new: almost every one of them is borrowed from simdjson, yyjson,
|
This post is about *how*. None of the techniques is new: almost every one of them is borrowed from simdjson, yyjson,
|
||||||
fast_float, Żmij, serde_json, SQLite, Boost.JSON or a textbook, and we name the source at each step. What we think is
|
fast_float, Żmij, serde_json, SQLite, Boost.JSON or a textbook, and we name the source at each step. What we think is
|
||||||
@@ -552,42 +553,77 @@ entries in document order instead of trees of separate allocations, and yyjson's
|
|||||||
returning the last one meant scanning every object to its end and made lookups in small objects 1.6 to 3.4 times
|
returning the last one meant scanning every object to its end and made lookups in small objects 1.6 to 3.4 times
|
||||||
slower ([#5622][5622]).
|
slower ([#5622][5622]).
|
||||||
|
|
||||||
Parse times in µs on the M1 Max (C++17, `-O3`, best of 30 interleaved rounds; a fresh `json_document` and yyjson
|
### How it compares
|
||||||
document each time, simdjson's parser reused as its documentation recommends):
|
|
||||||
|
|
||||||
| file | `json_document` | yyjson 0.13.0 | simdjson 4.6.11 DOM | Boost.JSON 1.92.0 | `json::parse` |
|
We compared `json_document` with eleven other engines. In C++: yyjson 0.13.0, simdjson 4.6.11 (its DOM and its
|
||||||
| --- | --: | --: | --: | --: | --: |
|
On-Demand API), sonic-cpp 1.0.2, RapidJSON (master, 24b5e7a), Glaze 9.0.0 (its `generic_u64` DOM, its `lazy_json`,
|
||||||
| `twitter.json` (632 KB) | **182** | 192 | 216 | 866 | 1,368 |
|
and reading straight into structs), and Boost.JSON (1.92 on the Mac, 1.83 on x86-64). In Rust: sonic-rs 0.5.10, simd-json 0.19.0 and serde_json
|
||||||
| `citm_catalog.json` (1.7 MB) | **360** | 478 | 490 | 1,480 | 2,409 |
|
1.0.154. Plus `json::parse`. Before anything is timed, every engine must report the same number of values, the same
|
||||||
| `canada.json` (2.3 MB) | **960** | 1,489 | 2,098 | 2,501 | 5,890 |
|
number of string bytes, and a bit-identical sum of all numbers in document order. That means every engine converts
|
||||||
| `jeopardy.json` (56 MB) | **22,687** | 30,528 | 26,875 | 88,900 | 167,664 |
|
every number exactly; RapidJSON guarantees that only with `kParseFullPrecisionFlag`, so that is what it runs with
|
||||||
|
(with its default flags it happened to produce the same values for these files, and those rows are in the data).
|
||||||
|
|
||||||
|
The workloads: *parse* (build the document and free it), *traverse* (parse, then visit every value and convert every
|
||||||
|
number), *select* (parse, then read a few fields of every record) and *parse+dump*. We ran them on the M1 Max with
|
||||||
|
Apple clang 21 (and Rust 1.99), and on the x86-64 Haswell server with GCC 13 and Clang 18 (`-march=haswell`, one
|
||||||
|
pinned core). That server has other work running, so differences of a few percent there are noise. Glaze 9 could
|
||||||
|
not be built with Clang 18 on it (Clang 18 can't use GCC 13's `std::expected`), and the Rust engines ran on the Mac
|
||||||
|
only.
|
||||||
|
|
||||||
|
Parse times in µs on the M1 Max, a fresh document each time (C++23 and Rust `--release`, `-O3`, best of 30
|
||||||
|
interleaved rounds):
|
||||||
|
|
||||||
|
| engine | `twitter.json` (632 KB) | `citm_catalog.json` (1.7 MB) | `canada.json` (2.3 MB) | `jeopardy.json` (56 MB) |
|
||||||
|
| --- | --: | --: | --: | --: |
|
||||||
|
| `json_document` | **183** | **364** | **943** | **22,681** |
|
||||||
|
| yyjson | 194 | 478 | 1,489 | 30,342 |
|
||||||
|
| simdjson DOM | 202 | 486 | 2,097 | 26,904 |
|
||||||
|
| sonic-rs (Rust) | 302 | 664 | 1,907 | 30,202 |
|
||||||
|
| sonic-cpp | 379 | 986 | 2,580 | 40,804 |
|
||||||
|
| simd-json (Rust, input copy included) | 567 | 1,616 | 4,939 | 59,364 |
|
||||||
|
| Boost.JSON | 868 | 1,483 | 2,481 | 88,925 |
|
||||||
|
| RapidJSON | 1,183 | 1,613 | 7,358 | 143,044 |
|
||||||
|
| serde_json (Rust) | 1,202 | 2,337 | 6,836 | 139,829 |
|
||||||
|
| Glaze `generic_u64` | 1,268 | 2,727 | 9,262 | 132,273 |
|
||||||
|
| `json::parse` | 1,352 | 2,386 | 5,915 | 163,750 |
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|
<div class="nj-fig-wrap" markdown>
|
||||||
--8<-- "images/perf_blog_view.svg"
|
--8<-- "images/perf_blog_view.svg"
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
On the same four files, reading every value takes yyjson 1.06 to 1.30 times as long as `json_document`, writing
|
The fastest *other* engine for each workload, as its time divided by `json_document`'s, over the four files above:
|
||||||
them back with `dump()` 1.06 to 1.35 times, and parsing, editing a few values and writing them 1.34 to 1.85 times (1.0 to 1.57 times for an edit in every
|
above 1, `json_document` is ahead; below 1, it is behind.
|
||||||
record). Integers are
|
|
||||||
already converted when you read them, and the writer copies strings and integer tokens straight from the source
|
|
||||||
([#5633][5633]).
|
|
||||||
|
|
||||||
It is not faster everywhere, and the harness ([#5627][5627], [#5914][5914]) is built to show where:
|
| workload | M1 Max, Apple clang 21 | x86-64, GCC 13 | x86-64, Clang 18 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| parse | 1.06–1.58 (yyjson, simdjson DOM) | 0.97–1.01 (simdjson DOM) on `twitter`, `citm_catalog`; 1.89 on `canada`; **0.71** (sonic-cpp) on `jeopardy` | 0.96–0.97 (simdjson DOM) on `twitter`, `citm_catalog`; 1.21–1.56 on `canada`, `jeopardy` |
|
||||||
|
| traverse | 1.04–1.18 (yyjson, simdjson On-Demand) | **0.58–0.86** (simdjson On-Demand) | **0.86**–1.20 (sonic-cpp, simdjson On-Demand, yyjson) |
|
||||||
|
| select | **0.34–0.84** (Glaze structs, simdjson On-Demand) | **0.26–0.65** | **0.33–0.86** |
|
||||||
|
| parse+dump | 1.18–1.35 (yyjson) | 1.20–1.41 (yyjson, sonic-cpp) | 1.16–1.27 (yyjson, sonic-cpp) |
|
||||||
|
|
||||||
- **Reading a few fields.** simdjson's On-Demand API builds nothing and can only read forward. Picking a few fields
|
So, is it the fastest JSON parser? Not in general. Among the parsers that build a document you can read in any
|
||||||
out of `twitter.json`, `canada.json` and `jeopardy.json` takes it 0.67 to 0.80 times as long as `json_document`.
|
order, it is the fastest at parsing and writing back in every configuration we ran, and the fastest at parsing on
|
||||||
- **Floats are converted later.** `json_document` converts a float when you read it; yyjson and simdjson convert
|
Apple silicon. On x86-64 it ties with simdjson's DOM on typical documents. Where it loses:
|
||||||
every number while parsing. For `canada.json`, which is almost all floats, the parse row therefore flatters
|
|
||||||
`json_document` (1.55× ahead of yyjson); reading every value after parsing is the fair comparison, and there it
|
- **Reading a few known fields.** simdjson's On-Demand API builds nothing and reads forward only; Glaze reading into
|
||||||
is 1.07× ahead.
|
structs that declare just the needed fields skips everything else without building it. Both are faster for
|
||||||
- **Other CPUs.** On the x86-64 machine with GCC, simdjson's DOM with AVX2 was 19 % ahead on parsing
|
*select*, Glaze by up to 3.8× on `canada.json`, where it skips the coordinates.
|
||||||
`twitter.json` and 7 % on `citm_catalog.json` ([#5627][5627]), and yyjson's `dump()` of `canada.json` still is
|
- **One pass over everything on x86-64 with GCC.** simdjson On-Demand visits every value in 0.58 to 0.86 of the time.
|
||||||
ahead: `json_document` takes 1.4 times as long there ([#5908][5908]). Our tables are from one Apple silicon
|
- **Fresh large documents on Linux.** A fresh 56 MB `jeopardy.json` pays for the page faults of a new index of
|
||||||
machine.
|
3.25 million 16-byte nodes (52 MB). With GCC, sonic-cpp parses it in 0.71 of the time. Reusing the document (`read()`) halves `json_document`'s time
|
||||||
- **Small documents.** For a 2.5 KB status message, all three engines parse in about a microsecond, and yyjson is
|
there, and then it is ahead of a reused simdjson parser (0.53 against 0.61 of the fresh time).
|
||||||
6 % ahead. For a 94-byte message, the engines trade places between runs.
|
- **Float-heavy documents, measured fairly.** `json_document` converts a float when you read it; the others convert
|
||||||
- **`json::parse` itself.** Boost.JSON builds a mutable tree, as `json` does, so it is the fair comparison for
|
while parsing. The `canada.json` parse row flatters it; *traverse* is the fair comparison, and there it is
|
||||||
`json::parse`, not for `json_document`. It is still 1.6 to 2.4 times as fast.
|
1.04× ahead on the Mac and behind sonic-cpp with Clang on x86-64 (0.88).
|
||||||
|
- **Small documents.** On a 2.5 KB message, all document parsers take about a microsecond, and yyjson and
|
||||||
|
simdjson's DOM are as fast or a few percent faster.
|
||||||
|
|
||||||
|
And `json::parse`? Boost.JSON builds a mutable tree as `json` does, so it is the fair comparison. It is 1.6 to 2.4
|
||||||
|
times as fast on the Mac, and 2.4 to 4.5 times on the x86-64 server.
|
||||||
|
|
||||||
|
The programs are in
|
||||||
|
[`tests/benchmarks/json_view/others`](https://github.com/nlohmann/json/tree/develop/tests/benchmarks/json_view/others),
|
||||||
|
with the pinned versions and checksums of every library.
|
||||||
|
|
||||||
## What didn't work
|
## What didn't work
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<svg class="nj-fig nj-charts nj-charts--blog" viewBox="0 0 580 404" role="img" aria-labelledby="nj-blog-view nj-blog-view-d" xmlns="http://www.w3.org/2000/svg">
|
<svg class="nj-fig nj-charts nj-charts--blog" viewBox="0 0 580 472" role="img" aria-labelledby="nj-blog-view nj-blog-view-d" xmlns="http://www.w3.org/2000/svg">
|
||||||
<title id="nj-blog-view">Parse throughput of json_document, yyjson, simdjson's D​OM, Boost.J​SON and json::parse, Apple M1 Max</title>
|
<title id="nj-blog-view">Parse throughput of json_document and six other document parsers, fresh documents, Apple M1 Max</title>
|
||||||
<desc id="nj-blog-view-d">Grouped horizontal bars in GB/s. twitter.json json_document 3.48 GB/s; twitter.json yyjson 3.29 GB/s; twitter.json simdjson D​OM 2.92 GB/s; twitter.json Boost.J​SON 0.73 GB/s; twitter.json json::parse 0.46 GB/s; citm_catalog.json json_document 4.80 GB/s; citm_catalog.json yyjson 3.61 GB/s; citm_catalog.json simdjson D​OM 3.52 GB/s; citm_catalog.json Boost.J​SON 1.17 GB/s; citm_catalog.json json::parse 0.72 GB/s; canada.json json_document 2.35 GB/s; canada.json yyjson 1.51 GB/s; canada.json simdjson D​OM 1.07 GB/s; canada.json Boost.J​SON 0.90 GB/s; canada.json json::parse 0.38 GB/s; jeopardy.json json_document 2.45 GB/s; jeopardy.json yyjson 1.82 GB/s; jeopardy.json simdjson D​OM 2.07 GB/s; jeopardy.json Boost.J​SON 0.62 GB/s; jeopardy.json json::parse 0.33 GB/s.</desc>
|
<desc id="nj-blog-view-d">Grouped horizontal bars in GB/s. twitter.json json_document 3.45 GB/s; twitter.json yyjson 3.26 GB/s; twitter.json simdjson D​OM 3.13 GB/s; twitter.json sonic-rs (Rust) 2.09 GB/s; twitter.json sonic-cpp 1.67 GB/s; twitter.json Boost.J​SON 0.73 GB/s; twitter.json json::parse 0.47 GB/s; citm_catalog.json json_document 4.75 GB/s; citm_catalog.json yyjson 3.61 GB/s; citm_catalog.json simdjson D​OM 3.55 GB/s; citm_catalog.json sonic-rs (Rust) 2.60 GB/s; citm_catalog.json sonic-cpp 1.75 GB/s; citm_catalog.json Boost.J​SON 1.16 GB/s; citm_catalog.json json::parse 0.72 GB/s; canada.json json_document 2.39 GB/s; canada.json yyjson 1.51 GB/s; canada.json simdjson D​OM 1.07 GB/s; canada.json sonic-rs (Rust) 1.18 GB/s; canada.json sonic-cpp 0.87 GB/s; canada.json Boost.J​SON 0.91 GB/s; canada.json json::parse 0.38 GB/s; jeopardy.json json_document 2.45 GB/s; jeopardy.json yyjson 1.83 GB/s; jeopardy.json simdjson D​OM 2.06 GB/s; jeopardy.json sonic-rs (Rust) 1.84 GB/s; jeopardy.json sonic-cpp 1.36 GB/s; jeopardy.json Boost.J​SON 0.62 GB/s; jeopardy.json json::parse 0.34 GB/s.</desc>
|
||||||
<rect class="nj-charts-c1" x="8" y="10" width="12" height="12" rx="3"/>
|
<rect class="nj-charts-c1" x="8" y="10" width="12" height="12" rx="3"/>
|
||||||
<text class="nj-charts-leg" x="26" y="20.5">json_document</text>
|
<text class="nj-charts-leg" x="26" y="20.5">json_document</text>
|
||||||
<rect class="nj-charts-c2" x="138.2" y="10" width="12" height="12" rx="3"/>
|
<rect class="nj-charts-c2" x="138.2" y="10" width="12" height="12" rx="3"/>
|
||||||
@@ -8,73 +8,93 @@
|
|||||||
<rect class="nj-charts-c3" x="216.6" y="10" width="12" height="12" rx="3"/>
|
<rect class="nj-charts-c3" x="216.6" y="10" width="12" height="12" rx="3"/>
|
||||||
<text class="nj-charts-leg" x="234.6" y="20.5">simdjson D​OM</text>
|
<text class="nj-charts-leg" x="234.6" y="20.5">simdjson D​OM</text>
|
||||||
<rect class="nj-charts-c4" x="339.4" y="10" width="12" height="12" rx="3"/>
|
<rect class="nj-charts-c4" x="339.4" y="10" width="12" height="12" rx="3"/>
|
||||||
<text class="nj-charts-leg" x="357.4" y="20.5">Boost.J​SON</text>
|
<text class="nj-charts-leg" x="357.4" y="20.5">sonic-rs (Rust)</text>
|
||||||
<rect class="nj-charts-c5" x="447.4" y="10" width="12" height="12" rx="3"/>
|
<rect class="nj-charts-c5" x="8" y="30" width="12" height="12" rx="3"/>
|
||||||
<text class="nj-charts-leg" x="465.4" y="20.5">json::parse</text>
|
<text class="nj-charts-leg" x="26" y="40.5">sonic-cpp</text>
|
||||||
<line class="nj-charts-grid" x1="182.3" y1="40" x2="182.3" y2="360"/>
|
<rect class="nj-charts-c6" x="108.6" y="30" width="12" height="12" rx="3"/>
|
||||||
<text class="nj-charts-tick" x="182.3" y="376">0.5</text>
|
<text class="nj-charts-leg" x="126.6" y="40.5">Boost.J​SON</text>
|
||||||
<line class="nj-charts-grid" x1="222.6" y1="40" x2="222.6" y2="360"/>
|
<rect class="nj-charts-c1 nj-charts-dim" x="216.6" y="30" width="12" height="12" rx="3"/>
|
||||||
<text class="nj-charts-tick" x="222.6" y="376">1</text>
|
<text class="nj-charts-leg" x="234.6" y="40.5">json::parse</text>
|
||||||
<line class="nj-charts-grid" x1="262.9" y1="40" x2="262.9" y2="360"/>
|
<line class="nj-charts-grid" x1="182.3" y1="60" x2="182.3" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="262.9" y="376">1.5</text>
|
<text class="nj-charts-tick" x="182.3" y="444">0.5</text>
|
||||||
<line class="nj-charts-grid" x1="303.2" y1="40" x2="303.2" y2="360"/>
|
<line class="nj-charts-grid" x1="222.6" y1="60" x2="222.6" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="303.2" y="376">2</text>
|
<text class="nj-charts-tick" x="222.6" y="444">1</text>
|
||||||
<line class="nj-charts-grid" x1="343.5" y1="40" x2="343.5" y2="360"/>
|
<line class="nj-charts-grid" x1="262.9" y1="60" x2="262.9" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="343.5" y="376">2.5</text>
|
<text class="nj-charts-tick" x="262.9" y="444">1.5</text>
|
||||||
<line class="nj-charts-grid" x1="383.8" y1="40" x2="383.8" y2="360"/>
|
<line class="nj-charts-grid" x1="303.2" y1="60" x2="303.2" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="383.8" y="376">3</text>
|
<text class="nj-charts-tick" x="303.2" y="444">2</text>
|
||||||
<line class="nj-charts-grid" x1="424.1" y1="40" x2="424.1" y2="360"/>
|
<line class="nj-charts-grid" x1="343.5" y1="60" x2="343.5" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="424.1" y="376">3.5</text>
|
<text class="nj-charts-tick" x="343.5" y="444">2.5</text>
|
||||||
<line class="nj-charts-grid" x1="464.4" y1="40" x2="464.4" y2="360"/>
|
<line class="nj-charts-grid" x1="383.8" y1="60" x2="383.8" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="464.4" y="376">4</text>
|
<text class="nj-charts-tick" x="383.8" y="444">3</text>
|
||||||
<line class="nj-charts-grid" x1="504.7" y1="40" x2="504.7" y2="360"/>
|
<line class="nj-charts-grid" x1="424.1" y1="60" x2="424.1" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="504.7" y="376">4.5</text>
|
<text class="nj-charts-tick" x="424.1" y="444">3.5</text>
|
||||||
<line class="nj-charts-grid" x1="545" y1="40" x2="545" y2="360"/>
|
<line class="nj-charts-grid" x1="464.4" y1="60" x2="464.4" y2="428"/>
|
||||||
<text class="nj-charts-tick" x="545" y="376">5</text>
|
<text class="nj-charts-tick" x="464.4" y="444">4</text>
|
||||||
<text class="nj-charts-tick" x="343.5" y="394">GB/s, longer is faster</text>
|
<line class="nj-charts-grid" x1="504.7" y1="60" x2="504.7" y2="428"/>
|
||||||
<text class="nj-charts-rowlabel" x="134" y="83.5" text-anchor="end">twitter.json</text>
|
<text class="nj-charts-tick" x="504.7" y="444">4.5</text>
|
||||||
<path class="nj-charts-c1" d="M142 44 H418.132 Q422.132 44 422.132 48 V51 Q422.132 55 418.132 55 H142 Z"><title>json_document, twitter.json: 3.48 GB/s</title></path>
|
<line class="nj-charts-grid" x1="545" y1="60" x2="545" y2="428"/>
|
||||||
<text class="nj-charts-val" x="426.132" y="53.5">3.48</text>
|
<text class="nj-charts-tick" x="545" y="444">5</text>
|
||||||
<path class="nj-charts-c2" d="M142 57 H403.104 Q407.104 57 407.104 61 V64 Q407.104 68 403.104 68 H142 Z"><title>yyjson, twitter.json: 3.29 GB/s</title></path>
|
<text class="nj-charts-tick" x="343.5" y="462">GB/s, longer is faster</text>
|
||||||
<text class="nj-charts-val" x="411.104" y="66.5">3.29</text>
|
<text class="nj-charts-rowlabel" x="134" y="109.5" text-anchor="end">twitter.json</text>
|
||||||
<path class="nj-charts-c3" d="M142 70 H373.213 Q377.213 70 377.213 74 V77 Q377.213 81 373.213 81 H142 Z"><title>simdjson D​OM, twitter.json: 2.92 GB/s</title></path>
|
<path class="nj-charts-c1" d="M142 64 H416.142 Q420.142 64 420.142 68 V69 Q420.142 73 416.142 73 H142 Z"><title>json_document, twitter.json: 3.45 GB/s</title></path>
|
||||||
<text class="nj-charts-val" x="381.213" y="79.5">2.92</text>
|
<text class="nj-charts-val" x="424.142" y="71.5">3.45</text>
|
||||||
<path class="nj-charts-c4" d="M142 83 H196.776 Q200.776 83 200.776 87 V90 Q200.776 94 196.776 94 H142 Z"><title>Boost.J​SON, twitter.json: 0.73 GB/s</title></path>
|
<path class="nj-charts-c2" d="M142 75 H400.371 Q404.371 75 404.371 79 V80 Q404.371 84 400.371 84 H142 Z"><title>yyjson, twitter.json: 3.26 GB/s</title></path>
|
||||||
<text class="nj-charts-val" x="204.776" y="92.5">0.73</text>
|
<text class="nj-charts-val" x="408.371" y="82.5">3.26</text>
|
||||||
<path class="nj-charts-c5" d="M142 96 H175.208 Q179.208 96 179.208 100 V103 Q179.208 107 175.208 107 H142 Z"><title>json::parse, twitter.json: 0.46 GB/s</title></path>
|
<path class="nj-charts-c3" d="M142 86 H389.98 Q393.98 86 393.98 90 V91 Q393.98 95 389.98 95 H142 Z"><title>simdjson D​OM, twitter.json: 3.13 GB/s</title></path>
|
||||||
<text class="nj-charts-val" x="183.208" y="105.5">0.46</text>
|
<text class="nj-charts-val" x="397.98" y="93.5">3.13</text>
|
||||||
<text class="nj-charts-rowlabel" x="134" y="162.5" text-anchor="end">citm_catalog.json</text>
|
<path class="nj-charts-c4" d="M142 97 H306.543 Q310.543 97 310.543 101 V102 Q310.543 106 306.543 106 H142 Z"><title>sonic-rs (Rust), twitter.json: 2.09 GB/s</title></path>
|
||||||
<path class="nj-charts-c1" d="M142 123 H524.809 Q528.809 123 528.809 127 V130 Q528.809 134 524.809 134 H142 Z"><title>json_document, citm_catalog.json: 4.80 GB/s</title></path>
|
<text class="nj-charts-val" x="314.543" y="104.5">2.09</text>
|
||||||
<text class="nj-charts-val" x="532.809" y="132.5">4.80</text>
|
<path class="nj-charts-c5" d="M142 108 H272.301 Q276.301 108 276.301 112 V113 Q276.301 117 272.301 117 H142 Z"><title>sonic-cpp, twitter.json: 1.67 GB/s</title></path>
|
||||||
<path class="nj-charts-c2" d="M142 136 H429.179 Q433.179 136 433.179 140 V143 Q433.179 147 429.179 147 H142 Z"><title>yyjson, citm_catalog.json: 3.61 GB/s</title></path>
|
<text class="nj-charts-val" x="280.301" y="115.5">1.67</text>
|
||||||
<text class="nj-charts-val" x="437.179" y="145.5">3.61</text>
|
<path class="nj-charts-c6" d="M142 119 H196.641 Q200.641 119 200.641 123 V124 Q200.641 128 196.641 128 H142 Z"><title>Boost.J​SON, twitter.json: 0.73 GB/s</title></path>
|
||||||
<path class="nj-charts-c3" d="M142 149 H421.992 Q425.992 149 425.992 153 V156 Q425.992 160 421.992 160 H142 Z"><title>simdjson D​OM, citm_catalog.json: 3.52 GB/s</title></path>
|
<text class="nj-charts-val" x="204.641" y="126.5">0.73</text>
|
||||||
<text class="nj-charts-val" x="429.992" y="158.5">3.52</text>
|
<path class="nj-charts-c1 nj-charts-dim" d="M142 130 H175.648 Q179.648 130 179.648 134 V135 Q179.648 139 175.648 139 H142 Z"><title>json::parse, twitter.json: 0.47 GB/s</title></path>
|
||||||
<path class="nj-charts-c4" d="M142 162 H232.063 Q236.063 162 236.063 166 V169 Q236.063 173 232.063 173 H142 Z"><title>Boost.J​SON, citm_catalog.json: 1.17 GB/s</title></path>
|
<text class="nj-charts-val" x="183.648" y="137.5">0.47</text>
|
||||||
<text class="nj-charts-val" x="240.063" y="171.5">1.17</text>
|
<text class="nj-charts-rowlabel" x="134" y="200.5" text-anchor="end">citm_catalog.json</text>
|
||||||
<path class="nj-charts-c5" d="M142 175 H195.789 Q199.789 175 199.789 179 V182 Q199.789 186 195.789 186 H142 Z"><title>json::parse, citm_catalog.json: 0.72 GB/s</title></path>
|
<path class="nj-charts-c1" d="M142 155 H520.452 Q524.452 155 524.452 159 V160 Q524.452 164 520.452 164 H142 Z"><title>json_document, citm_catalog.json: 4.75 GB/s</title></path>
|
||||||
<text class="nj-charts-val" x="203.789" y="184.5">0.72</text>
|
<text class="nj-charts-val" x="528.452" y="162.5">4.75</text>
|
||||||
<text class="nj-charts-rowlabel" x="134" y="241.5" text-anchor="end">canada.json</text>
|
<path class="nj-charts-c2" d="M142 166 H429.24 Q433.24 166 433.24 170 V171 Q433.24 175 429.24 175 H142 Z"><title>yyjson, citm_catalog.json: 3.61 GB/s</title></path>
|
||||||
<path class="nj-charts-c1" d="M142 202 H327.093 Q331.093 202 331.093 206 V209 Q331.093 213 327.093 213 H142 Z"><title>json_document, canada.json: 2.35 GB/s</title></path>
|
<text class="nj-charts-val" x="437.24" y="173.5">3.61</text>
|
||||||
<text class="nj-charts-val" x="335.093" y="211.5">2.35</text>
|
<path class="nj-charts-c3" d="M142 177 H424.446 Q428.446 177 428.446 181 V182 Q428.446 186 424.446 186 H142 Z"><title>simdjson D​OM, citm_catalog.json: 3.55 GB/s</title></path>
|
||||||
<path class="nj-charts-c2" d="M142 215 H259.85 Q263.85 215 263.85 219 V222 Q263.85 226 259.85 226 H142 Z"><title>yyjson, canada.json: 1.51 GB/s</title></path>
|
<text class="nj-charts-val" x="432.446" y="184.5">3.55</text>
|
||||||
<text class="nj-charts-val" x="267.85" y="224.5">1.51</text>
|
<path class="nj-charts-c4" d="M142 188 H347.658 Q351.658 188 351.658 192 V193 Q351.658 197 347.658 197 H142 Z"><title>sonic-rs (Rust), citm_catalog.json: 2.60 GB/s</title></path>
|
||||||
<path class="nj-charts-c3" d="M142 228 H224.48 Q228.48 228 228.48 232 V235 Q228.48 239 224.48 239 H142 Z"><title>simdjson D​OM, canada.json: 1.07 GB/s</title></path>
|
<text class="nj-charts-val" x="355.658" y="195.5">2.60</text>
|
||||||
<text class="nj-charts-val" x="232.48" y="237.5">1.07</text>
|
<path class="nj-charts-c5" d="M142 199 H279.189 Q283.189 199 283.189 203 V204 Q283.189 208 279.189 208 H142 Z"><title>sonic-cpp, citm_catalog.json: 1.75 GB/s</title></path>
|
||||||
<path class="nj-charts-c4" d="M142 241 H210.545 Q214.545 241 214.545 245 V248 Q214.545 252 210.545 252 H142 Z"><title>Boost.J​SON, canada.json: 0.90 GB/s</title></path>
|
<text class="nj-charts-val" x="287.189" y="206.5">1.75</text>
|
||||||
<text class="nj-charts-val" x="218.545" y="250.5">0.90</text>
|
<path class="nj-charts-c6" d="M142 210 H231.872 Q235.872 210 235.872 214 V215 Q235.872 219 231.872 219 H142 Z"><title>Boost.J​SON, citm_catalog.json: 1.16 GB/s</title></path>
|
||||||
<path class="nj-charts-c5" d="M142 254 H168.804 Q172.804 254 172.804 258 V261 Q172.804 265 168.804 265 H142 Z"><title>json::parse, canada.json: 0.38 GB/s</title></path>
|
<text class="nj-charts-val" x="239.872" y="217.5">1.16</text>
|
||||||
<text class="nj-charts-val" x="176.804" y="263.5">0.38</text>
|
<path class="nj-charts-c1 nj-charts-dim" d="M142 221 H196.346 Q200.346 221 200.346 225 V226 Q200.346 230 196.346 230 H142 Z"><title>json::parse, citm_catalog.json: 0.72 GB/s</title></path>
|
||||||
<text class="nj-charts-rowlabel" x="134" y="320.5" text-anchor="end">jeopardy.json</text>
|
<text class="nj-charts-val" x="204.346" y="228.5">0.72</text>
|
||||||
<path class="nj-charts-c1" d="M142 281 H335.369 Q339.369 281 339.369 285 V288 Q339.369 292 335.369 292 H142 Z"><title>json_document, jeopardy.json: 2.45 GB/s</title></path>
|
<text class="nj-charts-rowlabel" x="134" y="291.5" text-anchor="end">canada.json</text>
|
||||||
<text class="nj-charts-val" x="343.369" y="290.5">2.45</text>
|
<path class="nj-charts-c1" d="M142 246 H330.402 Q334.402 246 334.402 250 V251 Q334.402 255 330.402 255 H142 Z"><title>json_document, canada.json: 2.39 GB/s</title></path>
|
||||||
<path class="nj-charts-c2" d="M142 294 H284.675 Q288.675 294 288.675 298 V301 Q288.675 305 284.675 305 H142 Z"><title>yyjson, jeopardy.json: 1.82 GB/s</title></path>
|
<text class="nj-charts-val" x="338.402" y="253.5">2.39</text>
|
||||||
<text class="nj-charts-val" x="292.675" y="303.5">1.82</text>
|
<path class="nj-charts-c2" d="M142 257 H259.85 Q263.85 257 263.85 261 V262 Q263.85 266 259.85 266 H142 Z"><title>yyjson, canada.json: 1.51 GB/s</title></path>
|
||||||
<path class="nj-charts-c3" d="M142 307 H304.612 Q308.612 307 308.612 311 V314 Q308.612 318 304.612 318 H142 Z"><title>simdjson D​OM, jeopardy.json: 2.07 GB/s</title></path>
|
<text class="nj-charts-val" x="267.85" y="264.5">1.51</text>
|
||||||
<text class="nj-charts-val" x="312.612" y="316.5">2.07</text>
|
<path class="nj-charts-c3" d="M142 268 H224.521 Q228.521 268 228.521 272 V273 Q228.521 277 224.521 277 H142 Z"><title>simdjson D​OM, canada.json: 1.07 GB/s</title></path>
|
||||||
<path class="nj-charts-c4" d="M142 320 H188.368 Q192.368 320 192.368 324 V327 Q192.368 331 188.368 331 H142 Z"><title>Boost.J​SON, jeopardy.json: 0.62 GB/s</title></path>
|
<text class="nj-charts-val" x="232.521" y="275.5">1.07</text>
|
||||||
<text class="nj-charts-val" x="196.368" y="329.5">0.62</text>
|
<path class="nj-charts-c4" d="M142 279 H233.141 Q237.141 279 237.141 283 V284 Q237.141 288 233.141 288 H142 Z"><title>sonic-rs (Rust), canada.json: 1.18 GB/s</title></path>
|
||||||
<path class="nj-charts-c5" d="M142 333 H164.706 Q168.706 333 168.706 337 V340 Q168.706 344 164.706 344 H142 Z"><title>json::parse, jeopardy.json: 0.33 GB/s</title></path>
|
<text class="nj-charts-val" x="241.141" y="286.5">1.18</text>
|
||||||
<text class="nj-charts-val" x="172.706" y="342.5">0.33</text>
|
<path class="nj-charts-c5" d="M142 290 H208.324 Q212.324 290 212.324 294 V295 Q212.324 299 208.324 299 H142 Z"><title>sonic-cpp, canada.json: 0.87 GB/s</title></path>
|
||||||
<line class="nj-charts-zero" x1="142" y1="40" x2="142" y2="360"/>
|
<text class="nj-charts-val" x="216.324" y="297.5">0.87</text>
|
||||||
|
<path class="nj-charts-c6" d="M142 301 H211.13 Q215.13 301 215.13 305 V306 Q215.13 310 211.13 310 H142 Z"><title>Boost.J​SON, canada.json: 0.91 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="219.13" y="308.5">0.91</text>
|
||||||
|
<path class="nj-charts-c1 nj-charts-dim" d="M142 312 H168.674 Q172.674 312 172.674 316 V317 Q172.674 321 168.674 321 H142 Z"><title>json::parse, canada.json: 0.38 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="176.674" y="319.5">0.38</text>
|
||||||
|
<text class="nj-charts-rowlabel" x="134" y="382.5" text-anchor="end">jeopardy.json</text>
|
||||||
|
<path class="nj-charts-c1" d="M142 337 H335.421 Q339.421 337 339.421 341 V342 Q339.421 346 335.421 346 H142 Z"><title>json_document, jeopardy.json: 2.45 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="343.421" y="344.5">2.45</text>
|
||||||
|
<path class="nj-charts-c2" d="M142 348 H285.574 Q289.574 348 289.574 352 V353 Q289.574 357 285.574 357 H142 Z"><title>yyjson, jeopardy.json: 1.83 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="293.574" y="355.5">1.83</text>
|
||||||
|
<path class="nj-charts-c3" d="M142 359 H304.433 Q308.433 359 308.433 363 V364 Q308.433 368 304.433 368 H142 Z"><title>simdjson D​OM, jeopardy.json: 2.06 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="312.433" y="366.5">2.06</text>
|
||||||
|
<path class="nj-charts-c4" d="M142 370 H286.258 Q290.258 370 290.258 374 V375 Q290.258 379 286.258 379 H142 Z"><title>sonic-rs (Rust), jeopardy.json: 1.84 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="294.258" y="377.5">1.84</text>
|
||||||
|
<path class="nj-charts-c5" d="M142 381 H247.737 Q251.737 381 251.737 385 V386 Q251.737 390 247.737 390 H142 Z"><title>sonic-cpp, jeopardy.json: 1.36 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="255.737" y="388.5">1.36</text>
|
||||||
|
<path class="nj-charts-c6" d="M142 392 H188.354 Q192.354 392 192.354 396 V397 Q192.354 401 188.354 401 H142 Z"><title>Boost.J​SON, jeopardy.json: 0.62 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="196.354" y="399.5">0.62</text>
|
||||||
|
<path class="nj-charts-c1 nj-charts-dim" d="M142 403 H165.345 Q169.345 403 169.345 407 V408 Q169.345 412 165.345 412 H142 Z"><title>json::parse, jeopardy.json: 0.34 GB/s</title></path>
|
||||||
|
<text class="nj-charts-val" x="173.345" y="410.5">0.34</text>
|
||||||
|
<line class="nj-charts-zero" x1="142" y1="60" x2="142" y2="428"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 11 KiB |
@@ -7,7 +7,7 @@ Generated files:
|
|||||||
json.hpp or json_view.hpp since 3.12.0, one panel each
|
json.hpp or json_view.hpp since 3.12.0, one panel each
|
||||||
- perf_blog_swar.svg: the eight-byte string test on one word of input
|
- perf_blog_swar.svg: the eight-byte string test on one word of input
|
||||||
- perf_blog_cliffs.svg: operations that were quadratic or worse, 3.12.0 against 3.13.0 (log scale)
|
- perf_blog_cliffs.svg: operations that were quadratic or worse, 3.12.0 against 3.13.0 (log scale)
|
||||||
- perf_blog_view.svg: parse throughput of json_document, yyjson, simdjson's DOM, Boost.JSON and json::parse
|
- perf_blog_view.svg: parse throughput of json_document and six other document parsers
|
||||||
|
|
||||||
The numbers are copied from the tables of the post (the timeline from data/perf_timeline.csv, which
|
The numbers are copied from the tables of the post (the timeline from data/perf_timeline.csv, which
|
||||||
`tests/benchmarks/history/history.py summarize` writes); the tables stay on the page as the accessible fallback. Update both together.
|
`tests/benchmarks/history/history.py summarize` writes); the tables stay on the page as the accessible fallback. Update both together.
|
||||||
@@ -303,12 +303,14 @@ def cliffs():
|
|||||||
# engines and GB/s per file, copied from the table in "Build a different data structure"
|
# engines and GB/s per file, copied from the table in "Build a different data structure"
|
||||||
VIEW_FILES = ["twitter.json", "citm_catalog.json", "canada.json", "jeopardy.json"]
|
VIEW_FILES = ["twitter.json", "citm_catalog.json", "canada.json", "jeopardy.json"]
|
||||||
VIEW_BYTES = [631514, 1727204, 2251051, 55554625]
|
VIEW_BYTES = [631514, 1727204, 2251051, 55554625]
|
||||||
VIEW_US = [ # (engine, css class, [µs per file])
|
VIEW_US = [ # (engine, css class, [µs per file]), fresh documents, from the table in "How it compares"
|
||||||
("json_document", "nj-charts-c1", [181.7, 359.9, 959.5, 22687]),
|
("json_document", "nj-charts-c1", [183, 364, 943, 22681]),
|
||||||
("yyjson", "nj-charts-c2", [192.0, 478.1, 1489, 30528]),
|
("yyjson", "nj-charts-c2", [194, 478, 1489, 30342]),
|
||||||
("simdjson DOM", "nj-charts-c3", [216.4, 490.2, 2098, 26875]),
|
("simdjson DOM", "nj-charts-c3", [202, 486, 2097, 26904]),
|
||||||
("Boost.JSON", "nj-charts-c4", [866.0, 1480, 2501, 88900]),
|
("sonic-rs (Rust)", "nj-charts-c4", [302, 664, 1907, 30202]),
|
||||||
("json::parse", "nj-charts-c5", [1368, 2409, 5890, 167664]),
|
("sonic-cpp", "nj-charts-c5", [379, 986, 2580, 40804]),
|
||||||
|
("Boost.JSON", "nj-charts-c6", [868, 1483, 2481, 88925]),
|
||||||
|
("json::parse", "nj-charts-c1 nj-charts-dim", [1352, 2386, 5915, 163750]),
|
||||||
]
|
]
|
||||||
VIEW = [(e, c, [n / (t * 1000) for n, t in zip(VIEW_BYTES, us)]) for e, c, us in VIEW_US] # GB/s
|
VIEW = [(e, c, [n / (t * 1000) for n, t in zip(VIEW_BYTES, us)]) for e, c, us in VIEW_US] # GB/s
|
||||||
|
|
||||||
@@ -318,15 +320,18 @@ def view():
|
|||||||
hi = max(max(v) for _, _, v in VIEW)
|
hi = max(max(v) for _, _, v in VIEW)
|
||||||
hi = math.ceil(hi * 2) / 2
|
hi = math.ceil(hi * 2) / 2
|
||||||
X = lambda v: L + v / hi * (R - L)
|
X = lambda v: L + v / hi * (R - L)
|
||||||
bh, gap = 11, 2
|
bh, gap = 9, 2
|
||||||
top = 44
|
top = 64
|
||||||
group_h = len(VIEW) * (bh + gap) + 14
|
group_h = len(VIEW) * (bh + gap) + 14
|
||||||
b, desc = [], []
|
b, desc = [], []
|
||||||
x = 8
|
x, ly = 8, 10
|
||||||
for j, (eng, cls, _) in enumerate(VIEW):
|
for j, (eng, cls, _) in enumerate(VIEW):
|
||||||
b.append('<rect class="%s" x="%g" y="10" width="12" height="12" rx="3"/>' % (cls, x))
|
w = 18 + 7.4 * len(eng) + 16
|
||||||
b.append('<text class="nj-charts-leg" x="%g" y="20.5">%s</text>' % (x + 18, esc(eng)))
|
if x + w > W:
|
||||||
x += 18 + 7.4 * len(eng) + 16
|
x, ly = 8, ly + 20
|
||||||
|
b.append('<rect class="%s" x="%g" y="%g" width="12" height="12" rx="3"/>' % (cls, x, ly))
|
||||||
|
b.append('<text class="nj-charts-leg" x="%g" y="%g">%s</text>' % (x + 18, ly + 10.5, esc(eng)))
|
||||||
|
x += w
|
||||||
axis_y = top + group_h * len(VIEW_FILES)
|
axis_y = top + group_h * len(VIEW_FILES)
|
||||||
t = 0.5
|
t = 0.5
|
||||||
while t <= hi + 1e-9:
|
while t <= hi + 1e-9:
|
||||||
@@ -344,7 +349,7 @@ def view():
|
|||||||
desc.append("%s %s %.2f GB/s" % (f, eng, vals[i]))
|
desc.append("%s %s %.2f GB/s" % (f, eng, vals[i]))
|
||||||
b.append('<line class="nj-charts-zero" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(0), top - 4, X(0), axis_y))
|
b.append('<line class="nj-charts-zero" x1="%g" y1="%g" x2="%g" y2="%g"/>' % (X(0), top - 4, X(0), axis_y))
|
||||||
write("perf_blog_view.svg", W, axis_y + 44, "nj-charts--blog", "nj-blog-view",
|
write("perf_blog_view.svg", W, axis_y + 44, "nj-charts--blog", "nj-blog-view",
|
||||||
"Parse throughput of json_document, yyjson, simdjson's DOM, Boost.JSON and json::parse, Apple M1 Max",
|
"Parse throughput of json_document and six other document parsers, fresh documents, Apple M1 Max",
|
||||||
"Grouped horizontal bars in GB/s. " + "; ".join(desc) + ".", b)
|
"Grouped horizontal bars in GB/s. " + "; ".join(desc) + ".", b)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# json_document against other JSON libraries
|
||||||
|
|
||||||
|
[`compare.py`](../compare.py) compares `json_document` with yyjson, simdjson and Boost.JSON. The programs here add
|
||||||
|
sonic-cpp, RapidJSON and Glaze in C++, and sonic-rs, simd-json and serde_json in Rust, for the post
|
||||||
|
[How 3.13.0 got faster](https://json.nlohmann.me/home/blog/how_3_13_0_got_faster/#how-it-compares). They are not
|
||||||
|
built by CMake or run by CI.
|
||||||
|
|
||||||
|
## What is measured
|
||||||
|
|
||||||
|
The same files and method as `bench_view.cpp`: `twitter.json`, `citm_catalog.json`, `canada.json`, `jeopardy.json`
|
||||||
|
and one status object of `twitter.json`; every engine runs interleaved in every round, each timed call after an
|
||||||
|
untimed one, and the best round is reported.
|
||||||
|
|
||||||
|
| workload | what it does |
|
||||||
|
|---|---|
|
||||||
|
| `parse` | build the document and free it |
|
||||||
|
| `traverse` | parse, then visit every value, convert every number, touch every string and key |
|
||||||
|
| `select` | parse, then read a few fields of every record (the fields are listed in `bench_view.cpp`) |
|
||||||
|
| `parse+dump` | parse, then write the document as compact JSON |
|
||||||
|
|
||||||
|
Before timing, every engine must report the same number of values, the same number of string bytes, and a sum of all
|
||||||
|
numbers in document order that is bit-identical to `json_document`'s (`json::parse` visits objects in key order and
|
||||||
|
is compared within 1e-9): every number is converted exactly. For *select*, all engines must return the same value.
|
||||||
|
|
||||||
|
| engine | version | notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `json_document` | this checkout | `json_view (reused)`: one document whose `read()` reuses its memory |
|
||||||
|
| yyjson | 0.13.0 | |
|
||||||
|
| simdjson | 4.6.11 | DOM with a reused parser (as recommended) and a fresh one; On-Demand for *traverse* and *select* |
|
||||||
|
| sonic-cpp | 1.0.2 | |
|
||||||
|
| RapidJSON | master 24b5e7a8b27f (2025-02-05) | `kParseFullPrecisionFlag` (correctly rounded floats); also default flags and in situ parsing with a copy of the input |
|
||||||
|
| Glaze | 9.0.0 | `generic_u64` (owning DOM), `lazy_json` (no validation), and for *select* reading into structs that declare only the fields used |
|
||||||
|
| Boost.JSON | 1.92.0 (Mac), 1.83 (system, x86-64) | monotonic resource |
|
||||||
|
| sonic-rs, simd-json, serde_json | see `rust/Cargo.lock` | simd-json parses in place: its times include copying the input; serde_json with `float_roundtrip` |
|
||||||
|
|
||||||
|
SHA-256 of the downloads:
|
||||||
|
|
||||||
|
```
|
||||||
|
dd7b033c5bf6e4308615bb7834c541291d5b20f64121d9a68222a39414635517 glaze v9.0.0.tar.gz
|
||||||
|
30edf9e47c417f653546c39295fdb66ba5f89fb37eb3780122c6ee66605e5837 sonic-cpp v1.0.2.tar.gz
|
||||||
|
2d2601a82d2d3b7e143a3c8d43ef616671391034bc46891a9816b79cf2d3e7a8 rapidjson 24b5e7a8b27f42fa16b96fc70aade9106cf7102f.tar.gz
|
||||||
|
34e0f62a2bc11ab20d601e8ca1cc2b2079503aa45119a19133d89d19b94a0fae yyjson 0.13.0.tar.gz
|
||||||
|
61d948fc24f0d793829ad658058e7597d064988a89b4607ea02e401a82df98ff simdjson v4.6.11.tar.gz
|
||||||
|
c4a3b310ddd2472416e091067166b0713be97c63f38c212c484ada022fd296ce boost_1_92_0.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building and running
|
||||||
|
|
||||||
|
All libraries except yyjson and simdjson are used header-only. With the downloads unpacked into `DL`
|
||||||
|
(`compare.py --download` fetches yyjson, simdjson and Boost into its build directory):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cc -O3 -DNDEBUG -c $DL/yyjson-0.13.0/src/yyjson.c -o yyjson.o
|
||||||
|
c++ -std=c++17 -O3 -DNDEBUG -c $DL/simdjson-4.6.11/singleheader/simdjson.cpp -o simdjson.o
|
||||||
|
c++ -std=c++23 -O3 -DNDEBUG -DRAPIDJSON_NEON \
|
||||||
|
-I ../../../../include -I $DL/yyjson-0.13.0/src -I $DL/simdjson-4.6.11/singleheader -I $DL/boost_1_92_0 \
|
||||||
|
-I $DL/glaze/include -I $DL/rapidjson/include -I $DL/sonic/include \
|
||||||
|
bench_all.cpp yyjson.o simdjson.o -o bench_all
|
||||||
|
./bench_all <json_test_data> 30 > mac-cpp-1.log
|
||||||
|
|
||||||
|
cd rust && cargo build --release && ./target/release/bench_rs <json_test_data> 30 > ../mac-rs-1.log
|
||||||
|
|
||||||
|
python3 summarize.py mac-cpp-*.log --rust mac-rs-*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
On x86-64, use `-march=haswell -DRAPIDJSON_SSE42` instead of `-DRAPIDJSON_NEON` (sonic-cpp needs AVX2), pin the
|
||||||
|
process to one core, and add the document name as third argument to time one document per process. Glaze 9 needs
|
||||||
|
`std::expected`, which Clang 18 cannot use from GCC 13's standard library; `-DBENCH_NO_GLAZE=1` builds without it.
|
||||||
|
|
||||||
|
## Reading the results
|
||||||
|
|
||||||
|
`summarize.py` prints, for every document and workload, the engines ordered by time and the fastest other engine
|
||||||
|
relative to `json_document`. Fresh documents are compared with fresh ones; the reused `json_document` is compared
|
||||||
|
with simdjson's reused parser. On a shared machine, differences of a few percent are noise: run twice and compare.
|
||||||
@@ -0,0 +1,852 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Parse/traverse/dump of the nativejson-benchmark files and jeopardy with every engine in one binary,
|
||||||
|
// interleaved, best round reported (same method as tests/benchmarks/json_view/bench_view.cpp).
|
||||||
|
// Engines: json_document (nlohmann), yyjson, simdjson DOM and On-Demand, RapidJSON, sonic-cpp,
|
||||||
|
// Glaze (generic_u64 DOM and lazy_json), Boost.JSON, json::parse.
|
||||||
|
// Usage: bench_all <json_test_data dir> [rounds] [document]
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
#include <glaze/glaze.hpp>
|
||||||
|
#include <glaze/json/lazy.hpp>
|
||||||
|
#endif
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
#include <rapidjson/stringbuffer.h>
|
||||||
|
#include <rapidjson/writer.h>
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <sonic/sonic.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- json::parse and json_view
|
||||||
|
static void walk(const json& j, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (j.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (const auto& kv : j.get_ref<const json::object_t&>())
|
||||||
|
{
|
||||||
|
st.str += kv.first.size();
|
||||||
|
walk(kv.second, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const auto& e : j.get_ref<const json::array_t&>()) walk(e, st);
|
||||||
|
break;
|
||||||
|
case json::value_t::string: st.str += j.get_ref<const std::string&>().size(); break;
|
||||||
|
case json::value_t::number_integer: st.num += static_cast<double>(*j.get_ptr<const json::number_integer_t*>()); break;
|
||||||
|
case json::value_t::number_unsigned: st.num += static_cast<double>(*j.get_ptr<const json::number_unsigned_t*>()); break;
|
||||||
|
case json::value_t::number_float: st.num += *j.get_ptr<const json::number_float_t*>(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v) walk(e, st);
|
||||||
|
break;
|
||||||
|
case json::value_t::string: st.str += v.get_string().size(); break;
|
||||||
|
case json::value_t::number_integer: st.num += static_cast<double>(v.get<std::int64_t>()); break;
|
||||||
|
case json::value_t::number_unsigned: st.num += static_cast<double>(v.get<std::uint64_t>()); break;
|
||||||
|
case json::value_t::number_float: st.num += v.get<double>(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- yyjson
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val *k, *val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val) walk(val, st);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR: st.str += yyjson_get_len(v); break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
if (yyjson_is_sint(v)) st.num += static_cast<double>(yyjson_get_sint(v));
|
||||||
|
else if (yyjson_is_uint(v)) st.num += static_cast<double>(yyjson_get_uint(v));
|
||||||
|
else st.num += yyjson_get_real(v);
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- simdjson DOM and On-Demand
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e)) walk(c, st);
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING: st.str += std::string_view(e).size(); break;
|
||||||
|
case simdjson::dom::element_type::INT64: st.num += static_cast<double>(int64_t(e)); break;
|
||||||
|
case simdjson::dom::element_type::UINT64: st.num += static_cast<double>(uint64_t(e)); break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE: st.num += double(e); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk_od(simdjson::ondemand::value v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::json_type::object:
|
||||||
|
for (auto f : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += std::string_view(f.unescaped_key()).size();
|
||||||
|
walk_od(f.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::array:
|
||||||
|
for (auto c : v.get_array()) walk_od(c.value(), st);
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::string: st.str += std::string_view(v.get_string()).size(); break;
|
||||||
|
case simdjson::ondemand::json_type::number:
|
||||||
|
{
|
||||||
|
simdjson::ondemand::number n = v.get_number();
|
||||||
|
switch (n.get_number_type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::number_type::signed_integer: st.num += static_cast<double>(n.get_int64()); break;
|
||||||
|
case simdjson::ondemand::number_type::unsigned_integer: st.num += static_cast<double>(n.get_uint64()); break;
|
||||||
|
default: st.num += n.get_double(); break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case simdjson::ondemand::json_type::boolean: (void)bool(v.get_bool()); break;
|
||||||
|
default: (void)v.is_null(); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- RapidJSON
|
||||||
|
static void walk(const rapidjson::Value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.GetType())
|
||||||
|
{
|
||||||
|
case rapidjson::kObjectType:
|
||||||
|
for (auto m = v.MemberBegin(); m != v.MemberEnd(); ++m)
|
||||||
|
{
|
||||||
|
st.str += m->name.GetStringLength();
|
||||||
|
walk(m->value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case rapidjson::kArrayType:
|
||||||
|
for (const auto& e : v.GetArray()) walk(e, st);
|
||||||
|
break;
|
||||||
|
case rapidjson::kStringType: st.str += v.GetStringLength(); break;
|
||||||
|
case rapidjson::kNumberType:
|
||||||
|
if (v.IsDouble()) st.num += v.GetDouble();
|
||||||
|
else if (v.IsInt64()) st.num += static_cast<double>(v.GetInt64());
|
||||||
|
else st.num += static_cast<double>(v.GetUint64());
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- sonic-cpp
|
||||||
|
template<class N>
|
||||||
|
static void walk_sonic(const N& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
if (v.IsObject())
|
||||||
|
{
|
||||||
|
for (auto m = v.MemberBegin(); m != v.MemberEnd(); ++m)
|
||||||
|
{
|
||||||
|
st.str += m->name.GetStringView().size();
|
||||||
|
walk_sonic(m->value, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (v.IsArray())
|
||||||
|
{
|
||||||
|
for (auto e = v.Begin(); e != v.End(); ++e) walk_sonic(*e, st);
|
||||||
|
}
|
||||||
|
else if (v.IsString())
|
||||||
|
{
|
||||||
|
st.str += v.GetStringView().size();
|
||||||
|
}
|
||||||
|
else if (v.IsNumber())
|
||||||
|
{
|
||||||
|
if (v.IsDouble()) st.num += v.GetDouble();
|
||||||
|
else if (v.IsUint64()) st.num += static_cast<double>(v.GetUint64());
|
||||||
|
else st.num += static_cast<double>(v.GetInt64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
// ---- Glaze generic_u64 (owning DOM) and lazy_json (on demand, no validation)
|
||||||
|
static void walk(const glz::generic_u64& j, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
std::visit([&](const auto& x)
|
||||||
|
{
|
||||||
|
using T = std::decay_t<decltype(x)>;
|
||||||
|
if constexpr (std::is_same_v<T, glz::generic_u64::object_t>)
|
||||||
|
{
|
||||||
|
for (const auto& [k, v] : x)
|
||||||
|
{
|
||||||
|
st.str += k.size();
|
||||||
|
walk(v, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, glz::generic_u64::array_t>)
|
||||||
|
{
|
||||||
|
for (const auto& e : x) walk(e, st);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) st.str += x.size();
|
||||||
|
else if constexpr (std::is_same_v<T, double> || std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>) st.num += static_cast<double>(x);
|
||||||
|
}, j.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class V>
|
||||||
|
static void walk_lazy(const V& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
if (v.is_object())
|
||||||
|
{
|
||||||
|
for (auto item : v)
|
||||||
|
{
|
||||||
|
st.str += item.key().size();
|
||||||
|
walk_lazy(item, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (v.is_array())
|
||||||
|
{
|
||||||
|
for (auto item : v) walk_lazy(item, st);
|
||||||
|
}
|
||||||
|
else if (v.is_string())
|
||||||
|
{
|
||||||
|
st.str += v.template get<std::string>().value().size();
|
||||||
|
}
|
||||||
|
else if (v.is_number())
|
||||||
|
{
|
||||||
|
const std::string_view raw = v.raw_json();
|
||||||
|
if (raw.find_first_of(".eE") != std::string_view::npos) st.num += v.template get<double>().value();
|
||||||
|
else if (raw[0] == '-') st.num += static_cast<double>(v.template get<std::int64_t>().value());
|
||||||
|
else st.num += static_cast<double>(v.template get<std::uint64_t>().value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---- Boost.JSON
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array()) walk(c, st);
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string: st.str += v.get_string().size(); break;
|
||||||
|
case boost::json::kind::int64: st.num += static_cast<double>(v.get_int64()); break;
|
||||||
|
case boost::json::kind::uint64: st.num += static_cast<double>(v.get_uint64()); break;
|
||||||
|
case boost::json::kind::double_: st.num += v.get_double(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- selective access (copied from bench_view.cpp)
|
||||||
|
template<class V>
|
||||||
|
static double pick_view(const std::string& name, V r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](V s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(s["id"].template get<std::uint64_t>());
|
||||||
|
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
||||||
|
acc += static_cast<double>(s["retweet_count"].template get<std::int64_t>());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (const V s : r["statuses"])
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (const V p : r["performances"])
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(p["id"].template get<std::uint64_t>() + p["eventId"].template get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(r["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const V g = r["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (const V q : r)
|
||||||
|
{
|
||||||
|
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].get_string().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(r["method"].get_string().size());
|
||||||
|
acc += static_cast<double>(r["params"]["minuend"].template get<std::int64_t>() + r["id"].template get<std::int64_t>());
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, json_view r)
|
||||||
|
{
|
||||||
|
return pick_view<json_view>(name, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, yyjson_val* r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
auto get = [](yyjson_val * o, const char* k)
|
||||||
|
{
|
||||||
|
return yyjson_obj_get(o, k);
|
||||||
|
};
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](yyjson_val * s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(s, "id")));
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(get(s, "user"), "screen_name")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(s, "retweet_count")));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* s;
|
||||||
|
yyjson_arr_foreach(get(r, "statuses"), idx, max, s)
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* p;
|
||||||
|
yyjson_arr_foreach(get(r, "performances"), idx, max, p)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(p, "id")) + yyjson_get_uint(get(p, "eventId")) + yyjson_arr_size(get(p, "seatCategories")));
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(yyjson_obj_size(get(r, "events")));
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
yyjson_val* g = get(yyjson_arr_get(get(r, "features"), 0), "geometry");
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "type")) + yyjson_get_len(get(g, "type")) + yyjson_arr_size(get(g, "coordinates")));
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* q;
|
||||||
|
yyjson_arr_foreach(r, idx, max, q)
|
||||||
|
{
|
||||||
|
acc += yyjson_equals_str(get(q, "round"), "Final Jeopardy!") ? 1 : 0;
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(q, "category")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "method")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(get(r, "params"), "minuend")) + yyjson_get_sint(get(r, "id")));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, simdjson::dom::element r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::dom::element s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : simdjson::dom::array(r["statuses"]))
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (auto p : simdjson::dom::array(r["performances"]))
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(p["id"]) + uint64_t(p["eventId"]) + simdjson::dom::array(p["seatCategories"]).size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(simdjson::dom::object(r["events"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
auto g = r["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(r["type"]).size() + std::string_view(g["type"]).size() + simdjson::dom::array(g["coordinates"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : simdjson::dom::array(r))
|
||||||
|
{
|
||||||
|
acc += std::string_view(q["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(std::string_view(q["category"]).size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(r["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(r["params"]["minuend"]) + int64_t(r["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick_od(const std::string& name, simdjson::ondemand::document& d)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::ondemand::object s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : d["statuses"])
|
||||||
|
{
|
||||||
|
one(s.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(d.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object ev = d["events"].get_object();
|
||||||
|
acc += static_cast<double>(ev.count_fields());
|
||||||
|
for (auto p : d["performances"])
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = p.get_object();
|
||||||
|
const auto a = uint64_t(o["eventId"]) + uint64_t(o["id"]);
|
||||||
|
simdjson::ondemand::array sc = o["seatCategories"].get_array();
|
||||||
|
acc += static_cast<double>(a + sc.count_elements());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["type"]).size());
|
||||||
|
auto g = d["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(g["type"]).size());
|
||||||
|
simdjson::ondemand::array co = g["coordinates"].get_array();
|
||||||
|
acc += static_cast<double>(co.count_elements());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : d)
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = q.get_object();
|
||||||
|
acc += static_cast<double>(std::string_view(o["category"]).size());
|
||||||
|
acc += std::string_view(o["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(d["params"]["minuend"]));
|
||||||
|
acc += static_cast<double>(int64_t(d["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
// Glaze reading into structs that declare only the fields the selection uses; unknown keys are skipped
|
||||||
|
namespace sel
|
||||||
|
{
|
||||||
|
struct user { std::string screen_name; };
|
||||||
|
struct status { std::uint64_t id{}; sel::user user; std::int64_t retweet_count{}; };
|
||||||
|
struct twitter { std::vector<status> statuses; };
|
||||||
|
struct performance { std::uint64_t id{}; std::uint64_t eventId{}; std::vector<glz::raw_json_view> seatCategories; };
|
||||||
|
struct citm { std::map<std::string, glz::raw_json_view> events; std::vector<performance> performances; };
|
||||||
|
struct geometry { std::string type; std::vector<glz::raw_json_view> coordinates; };
|
||||||
|
struct feature { sel::geometry geometry; };
|
||||||
|
struct canada { std::string type; std::vector<feature> features; };
|
||||||
|
struct question { std::string category; std::string round; };
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr glz::opts sel_opts{.error_on_unknown_keys = false};
|
||||||
|
|
||||||
|
static double pick_glaze_structs(const std::string& name, const std::string& s)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](const sel::status& x)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(x.id);
|
||||||
|
acc += static_cast<double>(x.user.screen_name.size());
|
||||||
|
acc += static_cast<double>(x.retweet_count);
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
sel::twitter t;
|
||||||
|
if (glz::read<sel_opts>(t, s)) return -1;
|
||||||
|
for (const auto& x : t.statuses) one(x);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sel::status x;
|
||||||
|
if (glz::read<sel_opts>(x, s)) return -1;
|
||||||
|
one(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
sel::citm c;
|
||||||
|
if (glz::read<sel_opts>(c, s)) return -1;
|
||||||
|
for (const auto& p : c.performances) acc += static_cast<double>(p.id + p.eventId + p.seatCategories.size());
|
||||||
|
acc += static_cast<double>(c.events.size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
sel::canada c;
|
||||||
|
if (glz::read<sel_opts>(c, s)) return -1;
|
||||||
|
acc += static_cast<double>(c.type.size() + c.features[0].geometry.type.size() + c.features[0].geometry.coordinates.size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
std::vector<sel::question> q;
|
||||||
|
if (glz::read<sel_opts>(q, s)) return -1;
|
||||||
|
for (const auto& x : q)
|
||||||
|
{
|
||||||
|
acc += x.round == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(x.category.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class D>
|
||||||
|
static double pick_glaze_lazy(const std::string& name, const D& d)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
auto one = [&](const auto& x)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(x["id"].template get<std::uint64_t>().value());
|
||||||
|
acc += static_cast<double>(x["user"]["screen_name"].template get<std::string>().value().size());
|
||||||
|
acc += static_cast<double>(x["retweet_count"].template get<std::int64_t>().value());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto x : d["statuses"]) one(x);
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
one(d.root());
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (auto p : d["performances"])
|
||||||
|
acc += static_cast<double>(p["id"].template get<std::uint64_t>().value() + p["eventId"].template get<std::uint64_t>().value() + p["seatCategories"].size());
|
||||||
|
acc += static_cast<double>(d["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
auto g = d["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(d["type"].template get<std::string>().value().size() + g["type"].template get<std::string>().value().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : d.root())
|
||||||
|
{
|
||||||
|
acc += q["round"].template get<std::string>().value() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].template get<std::string>().value().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool same(const stats& a, const stats& b)
|
||||||
|
{
|
||||||
|
return a.nodes == b.nodes && a.str == b.str && std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto rj_flags = rapidjson::kParseFullPrecisionFlag; // correctly rounded floats, as every other engine
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
const std::string T = std::string(argv[1]) + "/";
|
||||||
|
const int rounds = argc > 2 ? std::atoi(argv[2]) : 30;
|
||||||
|
const std::string only = argc > 3 ? argv[3] : ""; // time only this document (the check covers all)
|
||||||
|
struct doc
|
||||||
|
{
|
||||||
|
std::string name, text;
|
||||||
|
int batch;
|
||||||
|
};
|
||||||
|
std::vector<doc> docs;
|
||||||
|
for (const char* f : {"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"})
|
||||||
|
{
|
||||||
|
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
|
||||||
|
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
|
||||||
|
}
|
||||||
|
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
|
||||||
|
|
||||||
|
// correctness: every engine must see the same values as json::parse
|
||||||
|
bool all_ok = true;
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
stats ref;
|
||||||
|
walk(json::parse(dc.text), ref);
|
||||||
|
std::vector<std::pair<std::string, stats>> got(9, {"(not built)", stats{}});
|
||||||
|
{ auto d = json_document::parse(dc.text); walk(d.root(), got[0].second); got[0].first = "json_view"; }
|
||||||
|
{ yyjson_doc* d = yyjson_read(dc.text.data(), dc.text.size(), 0); walk(yyjson_doc_get_root(d), got[1].second); yyjson_doc_free(d); got[1].first = "yyjson"; }
|
||||||
|
{ simdjson::dom::parser p; walk(p.parse(dc.text).value(), got[2].second); got[2].first = "simdjson DOM"; }
|
||||||
|
{ simdjson::ondemand::parser p; simdjson::padded_string ps(dc.text); auto d = p.iterate(ps).value(); walk_od(d.get_value().value(), got[3].second); got[3].first = "simdjson OD"; }
|
||||||
|
{ rapidjson::Document d; d.Parse<rj_flags>(dc.text.data(), dc.text.size()); if (d.HasParseError()) std::puts("RapidJSON parse error"); walk(d, got[4].second); got[4].first = "RapidJSON"; }
|
||||||
|
{ rapidjson::Document d; d.Parse(dc.text.data(), dc.text.size()); stats x; walk(d, x); std::printf("info %-13s RapidJSON default flags: sum %s\n", dc.name.c_str(), x.num == got[0].second.num ? "exact" : "differs from the correctly rounded one"); }
|
||||||
|
{ sonic_json::Document d; d.Parse(dc.text.data(), dc.text.size()); if (d.HasParseError()) std::puts("sonic parse error"); walk_sonic(d, got[5].second); got[5].first = "sonic-cpp"; }
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
{ glz::generic_u64 j; auto ec = glz::read_json(j, dc.text); if (ec) std::puts("glaze parse error"); walk(j, got[6].second); got[6].first = "Glaze generic"; }
|
||||||
|
#endif
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
{ auto r = glz::lazy_json(dc.text); walk_lazy(r->root(), got[7].second); got[7].first = "Glaze lazy"; }
|
||||||
|
#endif
|
||||||
|
{ auto v = boost::json::parse(dc.text); walk(v, got[8].second); got[8].first = "Boost.JSON"; }
|
||||||
|
std::printf("check %-13s", dc.name.c_str());
|
||||||
|
for (const auto& g : got)
|
||||||
|
{
|
||||||
|
if (g.first == "(not built)") continue;
|
||||||
|
const bool ok = same(ref, g.second);
|
||||||
|
all_ok = all_ok && ok;
|
||||||
|
// every engine but json::parse (std::map order) visits the values in document order, so their sums
|
||||||
|
// must be bit-identical to json_view's if every number was converted to the same double
|
||||||
|
const bool exact = g.second.num == got[0].second.num;
|
||||||
|
std::printf(" %s:%s%s", g.first.c_str(), ok ? "OK" : "MISMATCH", exact ? "" : "(inexact)");
|
||||||
|
if (!ok) std::printf("(nodes %zu/%zu str %zu/%zu num %.17g/%.17g)", g.second.nodes, ref.nodes, g.second.str, ref.str, g.second.num, ref.num);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
const auto d = json_document::parse(dc.text);
|
||||||
|
const double ref = pick(dc.name, d.root());
|
||||||
|
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||||
|
const double py = pick(dc.name, yyjson_doc_get_root(y));
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
simdjson::ondemand::parser p;
|
||||||
|
simdjson::padded_string ps(dc.text);
|
||||||
|
auto od_doc = p.iterate(ps).value();
|
||||||
|
const double po = pick_od(dc.name, od_doc);
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
const double pg = pick_glaze_structs(dc.name, dc.text);
|
||||||
|
auto lz = glz::lazy_json(dc.text);
|
||||||
|
const double pl = pick_glaze_lazy(dc.name, *lz);
|
||||||
|
#else
|
||||||
|
const double pg = ref, pl = ref;
|
||||||
|
#endif
|
||||||
|
const bool ok = py == ref && po == ref && pg == ref && pl == ref;
|
||||||
|
all_ok = all_ok && ok;
|
||||||
|
std::printf("check %-13s select %s (json_view %.17g yyjson %.17g OD %.17g Glaze structs %.17g lazy %.17g)\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", ref, py, po, pg, pl);
|
||||||
|
}
|
||||||
|
if (!all_ok) std::printf("WARNING: some engines disagree, see above\n");
|
||||||
|
|
||||||
|
json_document reused; // read() reuses its memory, as simdjson DOM reuses its parser
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
simdjson::ondemand::parser od;
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
if (!only.empty() && dc.name != only) continue;
|
||||||
|
const std::string& s = dc.text;
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
std::vector<std::pair<std::string, std::vector<engine>>> workloads;
|
||||||
|
workloads.push_back({"parse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
{"RapidJSON", [&] { rapidjson::Document d; d.Parse<rj_flags>(s.data(), s.size()); g_sink = d.IsObject(); }},
|
||||||
|
{"RapidJSON (default flags)", [&] { rapidjson::Document d; d.Parse(s.data(), s.size()); g_sink = d.IsObject(); }},
|
||||||
|
{"RapidJSON (in situ, with copy)", [&] { std::string c = s; rapidjson::Document d; d.ParseInsitu<rj_flags>(&c[0]); g_sink = d.IsObject(); }},
|
||||||
|
{"sonic-cpp", [&] { sonic_json::Document d; d.Parse(s.data(), s.size()); g_sink = d.IsObject(); }},
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
{"Glaze generic", [&] { glz::generic_u64 j; auto ec = glz::read_json(j, s); g_sink = static_cast<double>(bool(ec)); }},
|
||||||
|
#endif
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); g_sink = static_cast<double>(j.size()); }},
|
||||||
|
}});
|
||||||
|
workloads.push_back({"traverse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
{"RapidJSON", [&] { rapidjson::Document d; d.Parse<rj_flags>(s.data(), s.size()); stats st; walk(d, st); g_sink = st.num; }},
|
||||||
|
{"RapidJSON (default flags)", [&] { rapidjson::Document d; d.Parse(s.data(), s.size()); stats st; walk(d, st); g_sink = st.num; }},
|
||||||
|
{"sonic-cpp", [&] { sonic_json::Document d; d.Parse(s.data(), s.size()); stats st; walk_sonic(d, st); g_sink = st.num; }},
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
{"Glaze generic", [&] { glz::generic_u64 j; auto ec = glz::read_json(j, s); stats st; walk(j, st); g_sink = st.num + static_cast<double>(bool(ec)); }},
|
||||||
|
#endif
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
{"Glaze lazy", [&] { auto r = glz::lazy_json(s); stats st; walk_lazy(r->root(), st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); stats st; walk(j, st); g_sink = st.num; }},
|
||||||
|
}});
|
||||||
|
{
|
||||||
|
const std::string name = dc.name;
|
||||||
|
workloads.push_back({"select", {
|
||||||
|
{"json_view", [&, name] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||||
|
{"json_view (reused)", [&, name] { reused.read(s); g_sink = pick(name, reused.root()); }},
|
||||||
|
{"yyjson", [&, name] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson OD", [&, name] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
{"Glaze structs", [&, name] { g_sink = pick_glaze_structs(name, s); }},
|
||||||
|
{"Glaze lazy", [&, name] { auto r = glz::lazy_json(s); g_sink = pick_glaze_lazy(name, *r); }},
|
||||||
|
#endif
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
workloads.push_back({"parse+dump", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); std::size_t n = 0; char* o = yyjson_write(d, 0, &n); g_sink = static_cast<double>(n); std::free(o); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(sj.parse(ps).value_unsafe()); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"RapidJSON", [&] { rapidjson::Document d; d.Parse<rj_flags>(s.data(), s.size()); rapidjson::StringBuffer sb; rapidjson::Writer<rapidjson::StringBuffer> w(sb); d.Accept(w); g_sink = static_cast<double>(sb.GetSize()); }},
|
||||||
|
{"sonic-cpp", [&] { sonic_json::Document d; d.Parse(s.data(), s.size()); sonic_json::WriteBuffer wb; d.Serialize(wb); g_sink = static_cast<double>(wb.Size()); }},
|
||||||
|
#if !BENCH_NO_GLAZE
|
||||||
|
{"Glaze generic", [&] { glz::generic_u64 j; (void)glz::read_json(j, s); std::string o; (void)glz::write_json(j, o); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
#endif
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); std::string o = boost::json::serialize(v); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); std::string o = j.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
}});
|
||||||
|
for (auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
const int r = s.size() > 10000000 ? std::max(3, rounds / 5) : rounds;
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
wl.second[k].fn(); // untimed: the previous engine's leftovers are cleaned up here
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
for (int b = 0; b < dc.batch; ++b) wl.second[k].fn();
|
||||||
|
best[k] = std::min(best[k], std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf("%-13s %-11s", dc.name.c_str(), wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
std::printf(" %s %.1fus (%.2fx)", wl.second[k].name.c_str(), best[k] / 1e3, best[k] / best[0]);
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+626
@@ -0,0 +1,626 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bench_rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde_json",
|
||||||
|
"simd-json",
|
||||||
|
"sonic-rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "faststr"
|
||||||
|
version = "0.2.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ca7d44d22004409a61c393afb3369c8f7bb74abcae49fe249ee01dcc3002113"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"rkyv",
|
||||||
|
"serde",
|
||||||
|
"simdutf8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "halfbrown"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c7ed2f2edad8a14c8186b847909a41fbb9c3eafa44f88bd891114ed5019da09"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.16.1",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.191"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "munge"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c"
|
||||||
|
dependencies = [
|
||||||
|
"munge_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "munge_macro"
|
||||||
|
version = "0.4.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptr_meta"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "743da816b98c921cdbe8628ef7381b76f25ecf4da599fc80aca90eae7ef70cc0"
|
||||||
|
dependencies = [
|
||||||
|
"ptr_meta_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ptr_meta_derive"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rancor"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b534442d0fcdb55d66f373d9cac6d33b6293a2335bc2136dbd06ce0e87d2572"
|
||||||
|
dependencies = [
|
||||||
|
"ptr_meta",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
|
||||||
|
dependencies = [
|
||||||
|
"ref-cast-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast-impl"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rend"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rkyv"
|
||||||
|
version = "0.8.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9776093b7ca170454ab1406954f7b7d97a57c51dc6c0642957fb2ef25c2d399"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
"indexmap",
|
||||||
|
"munge",
|
||||||
|
"ptr_meta",
|
||||||
|
"rancor",
|
||||||
|
"rend",
|
||||||
|
"rkyv_derive",
|
||||||
|
"tinyvec",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rkyv_derive"
|
||||||
|
version = "0.8.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.154"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-json"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8f2e4d523646f7da6dde3983bd18ede0babc21c258ab708b3dd93ea0259719d"
|
||||||
|
dependencies = [
|
||||||
|
"halfbrown",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simdutf8",
|
||||||
|
"value-trait",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simdutf8"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sonic-number"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c350a05f295f922fc153a3af8b443c10db70904a9291a624ddb6058c6110d0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sonic-rs"
|
||||||
|
version = "0.5.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "756586caeeb21126a57c17d1ef2b9386e2613ba784aae0f27104f5e9982335ec"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"bumpalo",
|
||||||
|
"bytes",
|
||||||
|
"cfg-if",
|
||||||
|
"faststr",
|
||||||
|
"itoa",
|
||||||
|
"ref-cast",
|
||||||
|
"serde",
|
||||||
|
"simdutf8",
|
||||||
|
"sonic-number",
|
||||||
|
"sonic-simd",
|
||||||
|
"thiserror",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sonic-simd"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f99e664ecd2d85a68c87e3c7a3cfe691f647ea9e835de984aba4d54a41f817d4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "value-trait"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "545a3d7089f11f1741d12963c057adf9443fc24325110a6a6daf582e825fd51b"
|
||||||
|
dependencies = [
|
||||||
|
"float-cmp",
|
||||||
|
"halfbrown",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.4+wasi-0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.9",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.129"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.57.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.63"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "bench_rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
simd-json = "*"
|
||||||
|
sonic-rs = "*"
|
||||||
|
serde_json = { version = "*", features = ["float_roundtrip"] }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
lto = "fat"
|
||||||
|
codegen-units = 1
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Same workloads and method as bench_all.cpp: parse (build + free), traverse (parse, then visit every
|
||||||
|
// value, convert every number, touch every string and key), parse+dump; every engine interleaved, an
|
||||||
|
// untimed call before each timed one, best round reported.
|
||||||
|
// simd-json parses in place, so its timings include copying the input into a mutable buffer.
|
||||||
|
use simd_json::prelude::*;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
|
struct Stats {
|
||||||
|
num: f64,
|
||||||
|
str_: usize,
|
||||||
|
nodes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_simd(v: &simd_json::BorrowedValue, st: &mut Stats) {
|
||||||
|
st.nodes += 1;
|
||||||
|
if let Some(o) = v.as_object() {
|
||||||
|
for (k, x) in o.iter() {
|
||||||
|
st.str_ += k.len();
|
||||||
|
walk_simd(x, st);
|
||||||
|
}
|
||||||
|
} else if let Some(a) = v.as_array() {
|
||||||
|
for x in a.iter() {
|
||||||
|
walk_simd(x, st);
|
||||||
|
}
|
||||||
|
} else if let Some(s) = v.as_str() {
|
||||||
|
st.str_ += s.len();
|
||||||
|
} else if v.is_u64() {
|
||||||
|
st.num += v.as_u64().unwrap() as f64;
|
||||||
|
} else if v.is_i64() {
|
||||||
|
st.num += v.as_i64().unwrap() as f64;
|
||||||
|
} else if v.is_f64() {
|
||||||
|
st.num += v.as_f64().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_sonic(v: &sonic_rs::Value, st: &mut Stats) {
|
||||||
|
use sonic_rs::{JsonContainerTrait, JsonValueTrait};
|
||||||
|
st.nodes += 1;
|
||||||
|
if let Some(o) = v.as_object() {
|
||||||
|
for (k, x) in o.iter() {
|
||||||
|
st.str_ += k.len();
|
||||||
|
walk_sonic(x, st);
|
||||||
|
}
|
||||||
|
} else if let Some(a) = v.as_array() {
|
||||||
|
for x in a.iter() {
|
||||||
|
walk_sonic(x, st);
|
||||||
|
}
|
||||||
|
} else if let Some(s) = v.as_str() {
|
||||||
|
st.str_ += s.len();
|
||||||
|
} else if v.is_number() {
|
||||||
|
if let Some(u) = v.as_u64() {
|
||||||
|
st.num += u as f64;
|
||||||
|
} else if let Some(i) = v.as_i64() {
|
||||||
|
st.num += i as f64;
|
||||||
|
} else {
|
||||||
|
st.num += v.as_f64().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_serde(v: &serde_json::Value, st: &mut Stats) {
|
||||||
|
st.nodes += 1;
|
||||||
|
match v {
|
||||||
|
serde_json::Value::Object(o) => {
|
||||||
|
for (k, x) in o.iter() {
|
||||||
|
st.str_ += k.len();
|
||||||
|
walk_serde(x, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::Array(a) => {
|
||||||
|
for x in a.iter() {
|
||||||
|
walk_serde(x, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::String(s) => st.str_ += s.len(),
|
||||||
|
serde_json::Value::Number(n) => {
|
||||||
|
if let Some(u) = n.as_u64() {
|
||||||
|
st.num += u as f64;
|
||||||
|
} else if let Some(i) = n.as_i64() {
|
||||||
|
st.num += i as f64;
|
||||||
|
} else {
|
||||||
|
st.num += n.as_f64().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn same(a: &Stats, b: &Stats) -> bool {
|
||||||
|
a.nodes == b.nodes && a.str_ == b.str_ && (a.num - b.num).abs() <= 1e-9 * a.num.abs()
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut SINK: f64 = 0.0;
|
||||||
|
fn sink(x: f64) {
|
||||||
|
unsafe { std::ptr::write_volatile(std::ptr::addr_of_mut!(SINK), x) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let t = format!("{}/", args[1]);
|
||||||
|
let rounds: usize = args.get(2).map(|r| r.parse().unwrap()).unwrap_or(30);
|
||||||
|
let mut docs: Vec<(String, String, usize)> = Vec::new();
|
||||||
|
for f in ["nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"] {
|
||||||
|
let name = f.split('/').nth(1).unwrap().trim_end_matches(".json").to_string();
|
||||||
|
docs.push((name, std::fs::read_to_string(format!("{}{}", t, f)).unwrap(), 1));
|
||||||
|
}
|
||||||
|
let tw: serde_json::Value = serde_json::from_str(&docs[0].1).unwrap();
|
||||||
|
docs.push(("status".to_string(), serde_json::to_string(&tw["statuses"][0]).unwrap(), 200));
|
||||||
|
|
||||||
|
for (name, text, _) in &docs {
|
||||||
|
let mut r = Stats::default();
|
||||||
|
walk_serde(&serde_json::from_str(text).unwrap(), &mut r);
|
||||||
|
let mut a = Stats::default();
|
||||||
|
let mut buf = text.as_bytes().to_vec();
|
||||||
|
walk_simd(&simd_json::to_borrowed_value(&mut buf).unwrap(), &mut a);
|
||||||
|
let mut b = Stats::default();
|
||||||
|
walk_sonic(&sonic_rs::from_str(text).unwrap(), &mut b);
|
||||||
|
// sonic-rs keeps document order: its sum is compared bit for bit by the caller with json_view's
|
||||||
|
println!("check {:<13} simd-json:{} sonic-rs:{} serde_json:OK (nodes {} str {} num {:.17e} sonic-num {:.17e})",
|
||||||
|
name, if same(&r, &a) { "OK" } else { "MISMATCH" }, if same(&r, &b) { "OK" } else { "MISMATCH" }, r.nodes, r.str_, r.num, b.num);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (name, s, batch) in &docs {
|
||||||
|
type Eng<'a> = (&'static str, Box<dyn Fn() + 'a>);
|
||||||
|
let workloads: Vec<(&str, Vec<Eng>)> = vec![
|
||||||
|
("parse", vec![
|
||||||
|
("simd-json", Box::new(|| { let mut b = s.as_bytes().to_vec(); let v = simd_json::to_borrowed_value(&mut b).unwrap(); sink(v.is_object() as u8 as f64); })),
|
||||||
|
("sonic-rs", Box::new(|| { let v: sonic_rs::Value = sonic_rs::from_str(s).unwrap(); sink(sonic_rs::JsonValueTrait::is_object(&v) as u8 as f64); })),
|
||||||
|
("serde_json", Box::new(|| { let v: serde_json::Value = serde_json::from_str(s).unwrap(); sink(v.is_object() as u8 as f64); })),
|
||||||
|
]),
|
||||||
|
("traverse", vec![
|
||||||
|
("simd-json", Box::new(|| { let mut b = s.as_bytes().to_vec(); let v = simd_json::to_borrowed_value(&mut b).unwrap(); let mut st = Stats::default(); walk_simd(&v, &mut st); sink(st.num); })),
|
||||||
|
("sonic-rs", Box::new(|| { let v: sonic_rs::Value = sonic_rs::from_str(s).unwrap(); let mut st = Stats::default(); walk_sonic(&v, &mut st); sink(st.num); })),
|
||||||
|
("serde_json", Box::new(|| { let v: serde_json::Value = serde_json::from_str(s).unwrap(); let mut st = Stats::default(); walk_serde(&v, &mut st); sink(st.num); })),
|
||||||
|
]),
|
||||||
|
("parse+dump", vec![
|
||||||
|
("simd-json", Box::new(|| { let mut b = s.as_bytes().to_vec(); let v = simd_json::to_borrowed_value(&mut b).unwrap(); let o = simd_json::to_string(&v).unwrap(); sink(o.len() as f64); })),
|
||||||
|
("sonic-rs", Box::new(|| { let v: sonic_rs::Value = sonic_rs::from_str(s).unwrap(); let o = sonic_rs::to_string(&v).unwrap(); sink(o.len() as f64); })),
|
||||||
|
("serde_json", Box::new(|| { let v: serde_json::Value = serde_json::from_str(s).unwrap(); let o = serde_json::to_string(&v).unwrap(); sink(o.len() as f64); })),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
for (wl, engines) in &workloads {
|
||||||
|
let r = if s.len() > 10_000_000 { std::cmp::max(3, rounds / 5) } else { rounds };
|
||||||
|
let mut best = vec![f64::MAX; engines.len()];
|
||||||
|
for _ in 0..r {
|
||||||
|
for (k, (_, f)) in engines.iter().enumerate() {
|
||||||
|
f();
|
||||||
|
let t0 = Instant::now();
|
||||||
|
for _ in 0..*batch {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
let ns = t0.elapsed().as_nanos() as f64 / *batch as f64;
|
||||||
|
best[k] = best[k].min(ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("{:<13} {:<11}", name, wl);
|
||||||
|
for (k, (n, _)) in engines.iter().enumerate() {
|
||||||
|
print!(" {} {:.1}us", n, best[k] / 1e3);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# __ _____ _____ _____
|
||||||
|
# __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
# | | |__ | | | | | | version 3.12.0
|
||||||
|
# |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""Summarize runs of bench_all and bench_rs: for every document and workload, the engines ordered by time, and the
|
||||||
|
fastest other engine relative to json_document (above 1: json_document is ahead).
|
||||||
|
|
||||||
|
Usage: summarize.py LOG... [--rust LOG...]
|
||||||
|
|
||||||
|
Several logs of the same program are combined by taking each cell's best time. Fresh documents are compared with
|
||||||
|
fresh ones: "json_view (reused)" and "simdjson DOM" (a reused parser) are left out of the "fastest other" column
|
||||||
|
and compared with each other instead.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
REUSED = {"json_view (reused)", "simdjson DOM"}
|
||||||
|
DOCS = ["twitter", "citm_catalog", "canada", "jeopardy", "status"]
|
||||||
|
WORKLOADS = ["parse", "traverse", "select", "parse+dump"]
|
||||||
|
|
||||||
|
|
||||||
|
def parse(path, suffix=""):
|
||||||
|
cells = {}
|
||||||
|
for line in open(path):
|
||||||
|
if line.startswith(("check", "info", "WARNING")):
|
||||||
|
continue
|
||||||
|
m = re.match(r"(\S+)\s+(\S+)\s+(.*)", line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
for engine, us in re.findall(r"\s*(.+?) ([\d.]+)us(?: \([\d.]+x\))?", m[3]):
|
||||||
|
cells[(m[1], m[2], engine.strip() + suffix)] = float(us)
|
||||||
|
return cells
|
||||||
|
|
||||||
|
|
||||||
|
def best(paths, suffix=""):
|
||||||
|
runs = [parse(p, suffix) for p in paths]
|
||||||
|
keys = set(runs[0]).intersection(*runs[1:])
|
||||||
|
return {k: min(r[k] for r in runs) for k in keys}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = sys.argv[1:]
|
||||||
|
cpp = args[:args.index("--rust")] if "--rust" in args else args
|
||||||
|
rust = args[args.index("--rust") + 1:] if "--rust" in args else []
|
||||||
|
cells = best(cpp)
|
||||||
|
if rust:
|
||||||
|
cells.update(best(rust, " (Rust)"))
|
||||||
|
for wl in WORKLOADS:
|
||||||
|
print("\n== " + wl)
|
||||||
|
for doc in DOCS:
|
||||||
|
cell = {e: v for (d, w, e), v in cells.items() if d == doc and w == wl}
|
||||||
|
if "json_view" not in cell:
|
||||||
|
continue
|
||||||
|
jv = cell["json_view"]
|
||||||
|
order = sorted(cell.items(), key=lambda x: x[1])
|
||||||
|
fresh = {e: v for e, v in cell.items() if e not in REUSED and e != "json_view"}
|
||||||
|
other, t = min(fresh.items(), key=lambda x: x[1])
|
||||||
|
line = "%-13s json_view %10.1f us fastest other: %s %.2f" % (doc, jv, other, t / jv)
|
||||||
|
if "json_view (reused)" in cell and "simdjson DOM" in cell:
|
||||||
|
line += " | reused: simdjson DOM %.2f" % (cell["simdjson DOM"] / cell["json_view (reused)"])
|
||||||
|
print(line)
|
||||||
|
print(" " + ", ".join("%s %.2f" % (e, v / jv) for e, v in order))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in new issue
Block a user