From a78e2a6b10ab987d92491d26a157c604c84ba207 Mon Sep 17 00:00:00 2001
From: Niels Lohmann Speedup of 3.13.0 over 3.12.0 for a selection of workloads: the old time divided by the
-new one, Google Benchmark suite on an Apple M1 Max, median of 8 runs. More is better; the vertical line at 1× is 3.12.0.
-The times are in [Appendix B](#b-3120-against-3130).
How many of the suite's 182 benchmarks got how much faster. The one below 1× is copying -`citm_catalog.json` without destroying the copy. Every row is in [Appendix B](#b-3120-against-3130).
+How many of the suite's 182 benchmarks got how much faster. The two below 1× copy +`citm_catalog.json` and `twitter.json` without destroying the copy (0.95× and 0.99×). Every row is in [Appendix +B](#b-3120-against-3130).
The median benchmark got 2.9× as fast. On an Intel MacBook Pro (Skylake), the median is 2.9× as well, but parsing -floating-point numbers gained more (`canada.json` 6.1× instead of 3.4×) and writing integers less (`dump()` 1.4–2.3× -instead of 2.2–3.8×); [Appendix B](#b-3120-against-3130) has both columns. Copying and comparing values gained the +floating-point numbers gained more (`canada.json` 6.1× instead of 4.1×) and writing integers less (`dump()` 1.4–2.3× +instead of 2.2–3.7×); [Appendix B](#b-3120-against-3130) has both columns. Copying and comparing values gained the least, and the largest gains come from fixing algorithms rather than tuning them (theme 7). @@ -104,7 +106,7 @@ Eight themes did most of the work: ## Where the time went: a timeline The suite compares two points. To see which change did what, we compiled one small benchmark against the header of each -of the 351 commits on `develop` that changed `json.hpp` or `json_view.hpp` from 3.12.0 to November 3, and of 3.13.0, and +of the 358 commits on `develop` that changed `json.hpp` or `json_view.hpp` from 3.12.0 to November 7, and of 3.13.0, and ran all of them interleaved. Four of the eight workloads we followed: - `parse()` of `citm_catalog.json`: the lexer's structure and whitespace handling, and building many small objects; @@ -131,8 +133,8 @@ larger steps mean something. -Speedup over 3.12.0 after each of the 351 commits that changed the headers up to November -3, from 3.12.0 (left) to 3.13.0 (right). More is better; each point is the median of 4 runs of one build, taken in +
Speedup over 3.12.0 after each of the 358 commits that changed the headers up to November +7, from 3.12.0 (left) to 3.13.0 (right). More is better; each point is the median of 4 runs of one build, taken in random order (with 4 runs, the mean of the two middle ones). The badges are theme numbers; "U" is a UTF-8 check that came and went. The other four workloads, and every labeled step with its pull request and size, are in [Appendix C](#c-the-timeline).
@@ -202,14 +204,16 @@ iterators over a `std::listThe four kinds of input the lexer distinguishes in 3.13.0. Everything on the fast paths -falls back to the byte path on anything unusual, so the result and the error messages stay the same.
+falls back to the byte path on anything unusual, so the result and the error messages stay the same. The `FILE*` +window needs glibc or Apple's C library; elsewhere, a `FILE*` is read one character at a time with one lock per +parse. ### Detecting the input in C++11 @@ -236,7 +240,8 @@ which also keeps MSVC from warning about constant conditions. `get()` still look token bookkeeping are now calls. For an adapter that can seek, their `std::true_type` overloads are empty and cost nothing, because the position and the text of a token are derived only when an error message needs them (theme 4). For a `std::string`, what remains is the branch on -`next_unget` and the adapter's bounds check and pointer increment. +`next_unget` and the adapter's bounds check and pointer increment. For the most common calls, not even that remains: +they don't use the lexer at all ([One loop instead of two](#one-loop-instead-of-two)). ### Contiguous input @@ -249,6 +254,43 @@ are scanned in bulk (themes malformed, so error messages and byte positions stay the same, with one deliberate correction (the column after a number at the end of a line). [Appendix J](#j-validation) describes how that was checked. +A pair of iterators is harder. Before C++20, `s.begin()` and `s.end()` of a `std::string` can't be told from the +iterators of any other random-access container, so `json::parse(s.begin(), s.end())` was read one character at a time, +while `json::parse(s)` got all the bulk scans. The library now matches the iterator types of `std::string` and of a +`std::vector` of one-byte integers exactly, and turns the pair into the pointer range it covers. That made +`parse(s.begin(), s.end())` 1.7–2.0× as fast on the M1, and `accept()` 2.7–3.1×. + +### One loop instead of two + +Even on a pointer range, the parser and the lexer still talked through an interface: the parser asks for a token, the +lexer finds it and hands it over, and the parser dispatches on its type. The lexer's state, the current character, a +pending `unget()`, and where the token starts, lives in members, so it goes through memory on every token. After all of +the above, `json::accept()` of `twitter.json` still took 2.8× as long as `json_document::accept()` (theme +8), which reads the same bytes. + +[yyjson](https://github.com/ibireme/yyjson)'s reader shows the alternative: one function that holds the input in a +local pointer and in which the grammar, the whitespace and the tokens are one loop. After a `:`, it tests for the +characters that can start a value right there, instead of returning a token type and dispatching on it. 3.13.0 adds +such a parser for input in one block of memory, `contiguous_parser`. Its stack of open arrays and objects is a 64-bit +word with one bit per level, so the kind of the enclosing container is a shift away and stays in a register; every 64 +levels, the word is pushed to a vector. It doesn't build values itself: it drives the library's own SAX handlers, so +`parse()` builds the same `json` with the same code as before. It is used by `parse()` without a callback, by +`accept()`, and by `sax_parse()` with the user's own handler, each with the default options; everything else keeps the +old parser. + +The hard part was the errors. A second parser would have to produce the same exception, message and position for +every malformed input, a large surface for small differences. So it doesn't report errors at all. It stops at the +token the old parser would reject, before it reports that token to the handler, and the old parser runs again on the +same input to report the error. `parse()` gives it a handler that only reports, and `sax_parse()` one that forwards +only `parse_error()` to the user's handler, so that no event arrives twice. An error at the end of a large input costs +`parse()` about 1.2× as much as before; valid input costs nothing extra. + +On the M1, `accept()` got 1.6–2.0× as fast, `sax_parse()` 1.4–1.5×, and `parse()` 10–16% faster, about the same with +GCC and Clang on x86-64. In `accept()`, which needs no values, the parser also skips the digits of a number without +computing its value, except for the rare float whose exponent may reach the largest `double`: another 16% on +`canada.json`. The cost is about 1,000 lines of header, and a translation unit that parses a string and dumps it +compiles 3% slower at `-O0` and 9% at `-O2`. + ### Streams An `std::istream` already has a buffer: the get area of its `streambuf`, `gptr()` to `egptr()`. The lexer now scans that @@ -276,7 +318,10 @@ Before the stream refills its get area, the adapter appends the part of the curr buffer; only an error message ever reads the token text back. Nothing is read ahead: the adapter advances `gptr()` by exactly what the lexer consumes, so the stream is left where it was before. On an M1 Max, parsing from an `std::ifstream` took 1.8–2.5× as long as reading the file into a string and parsing that, in the pull request's -measurement before the change; now it takes just 9–16% longer. +measurement before the change, and 9–16% longer after it. Since then, parsing a string got faster still, because the +fused parser of [One loop instead of two](#one-loop-instead-of-two) needs input in one block of memory, so a stream now +takes 25–38% longer than the string again (the file read with one `read()`, parsing `twitter.json`, +`citm_catalog.json`, and `canada.json`). ### `FILE*` @@ -284,11 +329,26 @@ measurement before the change; now it takes just 9–16% longer. `_lock_file` and `_fgetc_nolock` with the Microsoft runtime). `flockfile` is recursive, so the binary readers' `fread()` and a SAX handler's own `ftell()` keep working. Whereas parsing `citm_catalog.json` from a `FILE*` got 4.4× as fast on macOS in the pull request's measurement, with glibc it got only 1.1–1.3× as fast, because glibc's per-call locking is -already cheap. Two details: MemorySanitizer builds keep `fgetc`, because glibc's inline `getc_unlocked` reads a buffer -that uninstrumented glibc filled. And a SAX handler that waits for another thread using the same `FILE` now deadlocks, -as the documentation says. Both paths can be switched off, with +already cheap. + +That still left a call per character, while a stream had its get area scanned in bulk. But a `FILE` has a buffer too, +and `getc_unlocked()` is a macro that reads it: glibc's compares `_IO_read_ptr` with `_IO_read_end`, Apple's counts +down `_r` and advances `_p`. The adapter now reads those same fields and offers the lexer the rest of the buffer as a +window, as for a stream. It consumes exactly the characters `getc_unlocked()` would have returned, so the file is +left where it was before, and when the C library is about to refill the buffer, the part of the current token in it is +kept for error messages. The idea is gnulib's +[`freadptr()`](https://github.com/coreutils/gnulib/blob/master/lib/freadptr.c), which knows these fields for many C +libraries. Parsing from a `FILE*` got another 2.0–2.6× faster on the M1 and 1.7–2.2× with glibc on x86-64, and +`accept()` 2.9–4.4×; it is now about as fast as parsing from an `std::ifstream`. uClibc +defines `__GLIBC__` but has other fields, so it is excluded, and other C libraries keep reading one character at a +time. + +Two details: MemorySanitizer builds keep `fgetc`, because glibc's inline `getc_unlocked` reads a buffer that +uninstrumented glibc filled. And a SAX handler that waits for another thread using the same `FILE` now deadlocks, as the +documentation says. Both paths can be switched off, with [`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) and -[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md). +[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md); the second turns the `FILE` +window off as well, as it needs the lock for the whole parse. ## 2 Work on words, not bytes { #theme-2 } @@ -325,6 +385,20 @@ Hacks](https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord). The sub it can flag a byte *after* a real hit, but never one before it: the lowest flag is always right, and the number of trailing zero bits divided by eight is the index of the first special byte. +The code above is the readable form, and it does more work than it needs to. The `& ~x` of the zero-byte test only +keeps bytes from 0x80 out of the result, and `has_non_ascii` flags exactly those bytes anyway. So all three `& ~x` +terms can go, and the whole test becomes one expression: + +```cpp +return ((q - ones) | (b - ones) | (v - 0x2020202020202020ull) | v) & high; +``` + +The same test without the redundant `& ~x` terms.
+ +Clang finds this reduction on its own; GCC doesn't, and emitted 24 instructions per word instead of 16. With GCC on +x86-64, writing it out made validating `twitter.json` and `citm_catalog.json` 5–7% faster and `dump()` 2–3%. The +masks of both forms are the same for every pair of adjacent byte values and for 1.3 billion random words. +The NEON string scan: three comparisons for four classes of bytes, and a narrowing +
The NEON string scan: two comparisons for four classes of bytes, and a narrowing shift in place of `pmovmskb`.
On `twitter.json`, the vector scan made `json::parse()` 5% faster with Clang and 9% with GCC on an M1 Max; on long @@ -738,7 +819,12 @@ arrays `[1,2]` at most 1.5%. The parser checks for the expected `:` or `,` before it falls into the lexer's general token switch (−7% on `citm_catalog.json`, a contribution by [@mvanslobbe](https://github.com/mvanslobbe)), and [`to_cbor()`](../../api/basic_json/to_cbor.md) no longer creates a temporary `basic_json` for every object key it writes -(−55% on `twitter.json`, by [@alexprabhat99](https://github.com/alexprabhat99)). +(−55% on `twitter.json`, by [@alexprabhat99](https://github.com/alexprabhat99)). And the binary readers wrote every +byte of a string twice: they grew the string with `resize()`, which fills it with zeros, and then copied the bytes over +the zeros. The zero fill alone was 5% of the instructions of `from_cbor()`. A string that the input holds in one block +is now appended from it directly, after its claimed length has been checked against the input, so a huge claimed +length still allocates nothing. Reading `twitter.json` and `citm_catalog.json` from CBOR and MessagePack got 5–9% +faster on the M1, 3–9% on x86-64. !!! note "The same idea, measured twice" @@ -1006,6 +1092,15 @@ oa.commit(out - start);Encoding a run of integers through a cursor, with one capacity check for the whole run.
+The cursor needs the vector to be as long as the space it writes into, so the sink grew it with `resize()` to its whole +new capacity. That zeroes the new bytes, so every doubling zeroed megabytes of fresh memory at once: it faulted in all +the pages and pushed them out of the cache before the writer got there. The writers had gained much less on x86-64 than +on the M1 (writing an array of numbers as CBOR got 2.4× as fast on a Skylake laptop and 4.8× on the M1 Max), and this +was part of the reason: page faults, zeroing and copies on reallocation of a 9–80 MB output were 30–76% of the 3.13.0 +time on x86-64. The sink still reserves the same capacity, but now resizes the vector only to 64 KiB ahead of the +cursor, a window that is still in the cache when the writer's stores arrive. On x86-64, writing large arrays of numbers +got 9–26% faster; on the M1, 1–2.5%. + ### Integers without branches The last step also took the branches out of encoding an integer. After the bounds checks, mispredicted branches @@ -1291,9 +1386,8 @@ operating system. It is not neutral in every detail, though: fresh memory costs page faults. yyjson's normal read makes the same copy internally, which is why both ways take about as long. A server that validates a request and then passes it on needs the copy. A program that reads a file into a buffer it owns can skip it, so we also timed the in-place ways with the copy made before the timer starts. On the Mac, - yyjson then comes 2–4% closer and stays second, and on `twitter.json` it sums and selects 2–3% faster than - `json_document`. On x86-64, it ties `json_document` in *sum* with Clang and takes 1.06× and 1.19× its time with GCC 16 - and GCC 13. + yyjson then comes 3–5% closer, and on `twitter.json` it validates and selects 1–2% faster than `json_document`. On + x86-64, it ties `json_document` in *sum* with Clang and takes 1.04× and 1.15× its time with GCC 16 and GCC 13. - `json_document` reads the string in place and keeps pointing into it. A document that owns its text ([`parse_copy()`](../../api/basic_json_document/parse_copy.md)) costs one copy more: 3–5% on the Mac. - Reading from files and streams is not part of the comparison. @@ -1344,12 +1438,13 @@ lists only x86-64 Linux; we show it on the Mac anyway. ### Results on the Mac On the Apple M1 Max, among the ways that reject all four broken inputs, `json_document` has the lowest mean on the four -files in all six use cases, and yyjson is second in every column. That includes editing, where the editable document is +files in all six use cases, and yyjson is the next library in every column; in *validate*, `json::accept()` comes in +between (1.38× against yyjson's 1.41×). That includes editing, where the editable document is faster than yyjson's mutable document: the speed doesn't depend on being read-only. Some caveats belong next to that: -- **The lead is not the same on every file.** On `twitter.json`, yyjson is within 3–7% in *validate*, *sum*, and +- **The lead is not the same on every file.** On `twitter.json`, yyjson is within 4–7% in *validate*, *sum*, and *select*. Most of the lead comes from `citm_catalog.json`, the floats of `canada.json` (next point) and the size of `jeopardy.json`. - **Floats are converted only when read.** That helps `json_document` in *validate* and *select* on `canada.json`, where @@ -1357,44 +1452,45 @@ Some caveats belong next to that: only Boost.JSON gains much: it validates `canada.json` in 47% of the time. *sum* reads every number, so it is the fair comparison for floats; there, `json_document` is still slightly ahead of yyjson. - **Checking less pays in *select*.** If the input needn't be checked fully, simdjson's On-Demand API and Glaze reading - into structs select in 0.75× and 0.82× `json_document`'s time (Glaze is faster only on `citm_catalog.json` and - `canada.json`), and in 0.54–0.62× on x86-64: the right halves of the *select* column. + into structs select in 0.76× and 0.81× `json_document`'s time (Glaze is faster only on `citm_catalog.json` and + `canada.json`), and in 0.55–0.61× on x86-64: the right halves of the *select* column. - **Indentation.** Two of the four files are indented, which theme 2 helps. On minified copies of `twitter.json` and - `citm_catalog.json`, yyjson comes closer in *sum* (1.11 → 1.04) but not in *validate* (1.28 → 1.27), and + `citm_catalog.json`, yyjson comes closer in *sum* (1.11 → 1.01) and a little in *validate* (1.27 → 1.24), and `json_document` stays first. - **Number text can be copied.** With [`number_format::source`](../../api/basic_json_view/number_format.md), `json_document` writes each number by copying its text instead of formatting it, and yyjson and RapidJSON can read numbers as text and write them back unchanged. The output keeps the source's spelling of each number, which is all a - minifier needs. It makes writing `canada.json` with `json_document` 4.0× as fast and minifying it 1.9× as fast; the + minifier needs. It makes writing `canada.json` with `json_document` 4.1× as fast and minifying it 1.9× as fast; the "N" halves of *edit*, *minify*, and *write* show it. -- **Small documents.** On a 2.5 KB message, the fastest libraries take about a microsecond on the Mac and 1.2–2 on - x86-64. yyjson validates and sums it as fast as `json_document` on the Mac, and with GCC 16 on x86-64, simdjson and - yyjson sum it slightly faster. +- **Small documents.** On a 2.5 KB message, the fastest libraries take about a microsecond on the Mac and 1.1–2 on + x86-64. yyjson validates and sums it as fast as `json_document` on the Mac, and with GCC on x86-64, simdjson and + yyjson sum it as fast. ### Reading into structs A view can be converted into your own types with the same [`from_json()`](../../features/arbitrary_types.md) as `json`, including the ones the [`NLOHMANN_DEFINE_TYPE_*`](../../api/macros/nlohmann_define_type_non_intrusive.md) macros generate. That is as fast as picking the fields by hand from the view; only for the many small records of -`jeopardy.json` does it take 23% longer. On the Mac, Glaze, serde_json, and Boost.JSON's `parse_into`, reading into the -same structs and checking everything, take 1.7×, 2.2×, and 3.1× as long (geometric mean over the four files). On a +`jeopardy.json` does it take 22% longer. On the Mac, Glaze, serde_json, and Boost.JSON's `parse_into`, reading into the +same structs and checking everything, take 1.7×, 2.3×, and 3.1× as long (geometric mean over the four files). On a `json` value, reading into the structs takes about as long as `json::parse()` alone. ### nlohmann/json without `json_document` And nlohmann/json without `json_document`? `json` builds a tree of separate allocations, and it shows: for *select*, *edit*, and *minify* it takes several times as long as `json_document`, like Boost.JSON and RapidJSON. Where no tree is -needed, it is closer: on the Mac, a SAX handler is on par with Boost.JSON's, and -[`json::accept()`](../../api/basic_json/accept.md) takes 6% longer than Boost.JSON without number conversion (on x86-64, -Boost.JSON validates in 0.65–0.69× and sums in 0.75–0.79× the time). `dump()` writes faster than Boost.JSON, RapidJSON -and serde_json, and about as fast as Glaze. +needed, the fused parser ([One loop instead of two](#one-loop-instead-of-two)) puts it among the fastest: +[`json::accept()`](../../api/basic_json/accept.md) validates the four files in 1.38× `json_document`'s time on the Mac, +ahead of every other library, and a SAX handler sums them in 1.34×, behind only yyjson and simdjson. Boost.JSON takes +1.8× and 1.5× as long as `json` there, and 1.2–1.4× and 1.1–1.3× on x86-64. `dump()` writes faster than Boost.JSON, +RapidJSON and serde_json, and about as fast as Glaze. Much of Boost.JSON's lead in building a tree comes from its allocator: here it builds the tree in a -`monotonic_resource`, an arena that is freed in one go, which saves it 28–41%. With its default allocator, which +`monotonic_resource`, an arena that is freed in one go, which saves it 27–40%. With its default allocator, which allocates every string, array, and object on its own as `json` does, `boost::json::parse()` with precise numbers takes -as long as `json::parse()` on `twitter.json`, 5% longer on `citm_catalog.json`, and 17% less time on `canada.json` (on -the Mac, freeing the tree included). +13–17% longer than `json::parse()` on `twitter.json`, 19–20% longer on `citm_catalog.json`, and 2% less time on +`canada.json` (on the Mac, freeing the tree included). ### Results on x86-64 @@ -1412,30 +1508,34 @@ compilers disagree only about *sum*. the figure above. Less is better; hover over or tap a cell for the times and the way. The figures for GCC 13 and Clang 23 are in [Appendix E](#e-comparison-with-other-libraries). -`json_document` has the lowest mean in five of the six columns with GCC, where simdjson is ahead in *sum*, and in all -six with Clang. This machine is noisy, though: between two runs, a library's time relative to `json_document`'s moved by -up to 9–10% (95th percentile; 2.5% on the Mac). So Clang's 7% lead in *sum* over sonic-rs is no result, and without -`twitter.json`, sonic-rs is first. In *validate*, the next library takes 1.8–1.9× as long, or 1.6–1.7× if every library -may reuse its parser, because validating no longer builds an index (theme -8). Behind those means, it loses on single files: +`json_document` has the lowest mean in five of the six columns with every compiler. The exception is *sum*, where +simdjson is ahead with GCC, and `json` with a SAX handler with GCC 13 and Clang 23 (with Clang by 5%, all of it on +`jeopardy.json`; below). This machine is noisy, though: between two runs, a library's time relative to +`json_document`'s moved by up to 7–8% (95th percentile; 2.4% on the Mac), so a lead of 5% is no result. In *validate*, +the next library takes 1.8–2.0× as long, or 1.6–1.7× if every library may reuse its parser, because validating no +longer builds an index (theme 8). The only way in between is +`json::accept()` at 1.3–1.7×, which builds nothing either ([One loop instead of two](#one-loop-instead-of-two)). +Behind those means, `json_document` loses on single files: - **One pass over every value, with GCC.** simdjson's On-Demand API sums all numbers of `twitter.json`, - `citm_catalog.json`, and `canada.json` in 0.83–0.99× `json_document`'s time with GCC 16 (0.86–0.91× with GCC 13). With + `citm_catalog.json`, and `canada.json` in 0.76–0.97× `json_document`'s time with GCC 16 (0.79–0.92× with GCC 13). With Clang, `json_document` is ahead there, except on `canada.json`, where yyjson, which converts the floats during the - parse, is faster than converting them on access. + parse, is faster than converting them on access, and on `citm_catalog.json`, where `json`'s SAX handler and + Boost.JSON's are 1–3% faster. - **Fresh large documents.** For the 56 MB `jeopardy.json`, a fresh document needs a fresh 52 MB index, and on Linux the page faults of that much new memory are a large part of the time: copying the file into fresh memory alone takes about 50% as long as `json_document`'s parse. Boost.JSON's SAX parser and sonic-rs, which build nothing, sum it in - 0.66–0.72× the time. Reusing the document cuts `json_document`'s time by 40–43%, to 51–53 ms, less than sonic-rs's 60 - ms. Validating needs no index: `json_document::accept()` takes 35 ms, and no library validates `jeopardy.json` faster, - even reused (the next is simdjson's reused parser at 47–49 ms). On the Mac, a fresh document costs hardly more than a - reused one. -- **Writing floats.** yyjson writes `canada.json` in 0.87–0.97× the time (and Glaze, with GCC 13, in 0.96×), because - `json_document` first converts most floats from their text. Writing the source text instead (`number_format::source`, - the "N" half) avoids that. -- **simdjson's DOM.** It selects from `citm_catalog.json` 16% faster with GCC 16 and 1–4% faster with GCC 13 and Clang - 23, and from `twitter.json` 1–6% faster. Boost.JSON, Glaze, sonic-cpp, and yyjson are 1–3% ahead in a few more cells, - which is within the noise. + 0.64–0.71× the time, and `json`'s SAX handler in 0.70–0.75×. Reusing the document cuts `json_document`'s time by + 41–43%, to 51 ms, less than sonic-rs's 60 ms. Validating needs no index: `json_document::accept()` takes 33–35 ms, + `json::accept()` 39–40 ms, and no other library validates `jeopardy.json` faster, even reused (the next is simdjson's + reused parser at 48–52 ms). On the Mac, a fresh document costs hardly more than a reused one. +- **Writing floats.** yyjson writes `canada.json` in 0.84–0.93× the time (and Glaze with GCC 13 and `json`'s `dump()` + with GCC 16, which start from `double`s, in 0.98× and 0.99×), because `json_document` first converts most floats from + their text. Writing the source text instead (`number_format::source`, the "N" half) avoids that. +- **simdjson's DOM.** It selects from `citm_catalog.json` 19% faster with GCC 16 and 7–13% faster with GCC 13 and Clang + 23, and from `twitter.json` 2–4% faster with GCC. Boost.JSON, Glaze, sonic-rs, sonic-cpp, and yyjson are ahead in a + few more cells, by 1–10%: summing `citm_catalog.json` (Boost.JSON with GCC), and selecting from or writing + `jeopardy.json`. Most of that is within the noise. The full tables, with each library's variants, are in [Appendix E](#e-comparison-with-other-libraries). @@ -1444,7 +1544,8 @@ The full tables, with each library's variants, are in [Appendix E](#e-comparison The four files are the ones 3.13.0 was tuned on, so we also ran *validate*, *sum*, *minify*, and *write* on the 19 other files of simdjson's [`jsonexamples`](https://github.com/simdjson/simdjson-data/tree/master/jsonexamples). By geometric mean, `json_document` stays the fastest in *validate*, *minify*, and *write* on every compiler, and in *sum* with Clang -and on the Mac; with GCC, simdjson sums in 0.86–0.87× its time. File by file, for *validate*: +and on the Mac; with GCC, simdjson sums in 0.87–0.88× its time. `json::accept()` validates them in 1.4–1.6× the time +of `json_document` on x86-64, close to simdjson (1.44–1.46×), the fastest other library. File by file, for *validate*:Time per call with 3.12.0 and 3.13.0, Google Benchmark suite on an Apple M1 Max, -median of 8 runs; the speedup is the old time divided by the new one. The Intel column is the speedup on the Intel +median of 12 runs; the speedup is the old time divided by the new one. The Intel column is the speedup on the Intel MacBook Pro of [Appendix G](#g-how-we-measured). The parse rows do not include destroying the result (the suite pauses the timer for that).
@@ -1643,188 +1746,188 @@ the timer for that). | benchmark | 3.12.0 | 3.13.0 | speedup | Intel | | --- | --: | --: | --: | --: | - | `Accept/canada` | 12.9 ms | 2.35 ms | 5.51× | 13× | - | `Accept/citm_catalog` | 6.53 ms | 923 µs | 7.07× | 8.45× | - | `Accept/floats` | 116 ms | 22.7 ms | 5.10× | 12.2× | - | `Accept/jeopardy` | 221 ms | 59.3 ms | 3.72× | 6.64× | - | `Accept/signed_ints` | 117 ms | 28.3 ms | 4.15× | 4.99× | - | `Accept/small_signed_ints` | 57.5 ms | 18 ms | 3.19× | 3.90× | - | `Accept/twitter` | 2.51 ms | 484 µs | 5.20× | 6.89× | - | `Accept/unsigned_ints` | 116 ms | 29.7 ms | 3.91× | 4.95× | - | `BinaryToCbor/1024` | 298 ns | 107 ns | 2.79× | 3.67× | - | `BinaryToCbor/128` | 249 ns | 117 ns | 2.13× | 2.27× | - | `BinaryToCbor/16384` | 952 ns | 414 ns | 2.30× | 3.46× | - | `BinaryToCbor/16` | 245 ns | 49.6 ns | 4.93× | 6.33× | - | `BinaryToCbor/2048` | 328 ns | 125 ns | 2.62× | 3.74× | - | `BinaryToCbor/256` | 265 ns | 90.9 ns | 2.91× | 3.33× | - | `BinaryToCbor/32768` | 1.54 µs | 803 ns | 1.92× | 2.11× | - | `BinaryToCbor/32` | 252 ns | 50.4 ns | 4.99× | 6.24× | - | `BinaryToCbor/4096` | 407 ns | 177 ns | 2.29× | 3.48× | - | `BinaryToCbor/512` | 290 ns | 102 ns | 2.84× | 3.02× | - | `BinaryToCbor/64` | 255 ns | 84.6 ns | 3.01× | 3.78× | - | `BinaryToCbor/8192` | 514 ns | 256 ns | 2.01× | 3.30× | - | `BinaryToCbor/8` | 248 ns | 49.4 ns | 5.01× | 6.18× | - | `Copy/canada` | 3.78 ms | 2.93 ms | 1.29× | 1.17× | - | `Copy/citm_catalog` | 1.7 ms | 1.51 ms | 1.13× | 1.12× | - | `Copy/twitter` | 840 µs | 767 µs | 1.09× | 1.14× | - | `CopyAssign/canada` | 3.86 ms | 2.94 ms | 1.31× | 1.19× | - | `CopyAssign/citm_catalog` | 1.77 ms | 1.59 ms | 1.11× | 1.12× | - | `CopyAssign/twitter` | 885 µs | 791 µs | 1.12× | 1.07× | - | `CopyOnly/canada` | 1.6 ms | 1.58 ms | 1.02× | 1.00× | - | `CopyOnly/citm_catalog` | 786 µs | 817 µs | 0.96× | 0.93× | - | `CopyOnly/twitter` | 424 µs | 421 µs | 1.01× | 1.02× | - | `CopyRing/canada` | 3.86 ms | 3 ms | 1.29× | 1.20× | - | `CopyRing/citm_catalog` | 1.84 ms | 1.61 ms | 1.15× | 1.20× | - | `CopyRing/twitter` | 952 µs | 818 µs | 1.16× | 1.15× | - | `DestroyOnly/canada` | 2.22 ms | 1.4 ms | 1.58× | 1.32× | - | `DestroyOnly/citm_catalog` | 926 µs | 711 µs | 1.30× | 1.20× | - | `DestroyOnly/twitter` | 416 µs | 336 µs | 1.24× | 1.15× | - | `Diff/canada` | 39.8 ms | 3.8 ms | 10.5× | 11.2× | - | `Diff/citm_catalog` | 7.19 ms | 1.12 ms | 6.40× | 7.78× | - | `Diff/twitter` | 5.11 ms | 661 µs | 7.73× | 8.79× | - | `Dump/canada / -` | 6.95 ms | 2.34 ms | 2.98× | 3.34× | - | `Dump/canada / 4` | 9.1 ms | 3.1 ms | 2.93× | 2.82× | - | `Dump/citm_catalog / -` | 1.42 ms | 296 µs | 4.81× | 3.93× | - | `Dump/citm_catalog / 4` | 1.99 ms | 423 µs | 4.70× | 4.09× | - | `Dump/floats / -` | 64.3 ms | 23.8 ms | 2.71× | 2.50× | - | `Dump/floats / 4` | 72.6 ms | 24.6 ms | 2.95× | 2.71× | - | `Dump/jeopardy / -` | 207 ms | 31.7 ms | 6.51× | 3.75× | - | `Dump/jeopardy / 4` | 228 ms | 35.4 ms | 6.45× | 2.61× | - | `Dump/signed_ints / -` | 24.1 ms | 10.7 ms | 2.24× | 1.44× | - | `Dump/signed_ints / 4` | 32.5 ms | 11.3 ms | 2.87× | 2.01× | - | `Dump/small_signed_ints / -` | 18.1 ms | 8.12 ms | 2.23× | 1.81× | - | `Dump/small_signed_ints / 4` | 25.5 ms | 8.81 ms | 2.90× | 2.21× | - | `Dump/twitter / -` | 1.44 ms | 214 µs | 6.70× | 5.75× | - | `Dump/twitter / 4` | 1.63 ms | 253 µs | 6.45× | 5.18× | - | `Dump/unsigned_ints / -` | 24 ms | 8.12 ms | 2.95× | 1.58× | - | `Dump/unsigned_ints / 4` | 32.3 ms | 8.59 ms | 3.76× | 2.30× | - | `Equal/canada` | 474 µs | 349 µs | 1.36× | 1.38× | - | `Equal/citm_catalog` | 280 µs | 263 µs | 1.06× | 0.95× | - | `Equal/twitter` | 146 µs | 131 µs | 1.11× | 1.02× | - | `Flatten/canada` | 29.4 ms | 11.1 ms | 2.65× | 2.68× | - | `Flatten/citm_catalog` | 6.72 ms | 3.07 ms | 2.19× | 2.45× | - | `Flatten/twitter` | 3.66 ms | 2.11 ms | 1.74× | 2.19× | - | `FlattenOrdered/citm_catalog` | 262 ms | 1.43 ms | 184× | 185× | - | `FlattenOrdered/twitter` | 51.3 ms | 890 µs | 57.6× | 46.1× | - | `FromBinaryBuffer/bjdata / canada` | 8.82 ms | 4.17 ms | 2.12× | 2.02× | - | `FromBinaryBuffer/bjdata / twitter` | 3.32 ms | 976 µs | 3.40× | 2.87× | - | `FromBinaryBuffer/bjdata_optimized / canada` | 9.24 ms | 2.48 ms | 3.73× | 3.51× | - | `FromBinaryBuffer/bjdata_optimized / twitter` | 3.31 ms | 986 µs | 3.35× | 2.86× | - | `FromBinaryBuffer/bson / canada` | 10 ms | 4.32 ms | 2.33× | 2.20× | - | `FromBinaryBuffer/bson / citm_catalog` | 4.08 ms | 1.61 ms | 2.54× | 2.59× | - | `FromBinaryBuffer/bson / twitter` | 3.26 ms | 937 µs | 3.48× | 3.08× | - | `FromBinaryBuffer/cbor / canada` | 8.9 ms | 2.12 ms | 4.21× | 3.86× | - | `FromBinaryBuffer/cbor / citm_catalog` | 3.94 ms | 1.45 ms | 2.71× | 2.81× | - | `FromBinaryBuffer/cbor / floats` | 30.5 ms | 5.48 ms | 5.56× | 3.64× | - | `FromBinaryBuffer/cbor / jeopardy` | 373 ms | 115 ms | 3.25× | 3.43× | - | `FromBinaryBuffer/cbor / signed_ints` | 36.7 ms | 9.89 ms | 3.71× | 2.37× | - | `FromBinaryBuffer/cbor / twitter` | 3.23 ms | 953 µs | 3.39× | 2.83× | - | `FromBinaryBuffer/msgpack / canada` | 8.86 ms | 2.12 ms | 4.17× | 3.92× | - | `FromBinaryBuffer/msgpack / citm_catalog` | 3.85 ms | 1.45 ms | 2.65× | 2.94× | - | `FromBinaryBuffer/msgpack / jeopardy` | 373 ms | 111 ms | 3.36× | 3.51× | - | `FromBinaryBuffer/msgpack / twitter` | 3.24 ms | 955 µs | 3.39× | 3.02× | - | `FromBinaryBuffer/ubjson / canada` | 8.84 ms | 4.2 ms | 2.10× | 2.13× | - | `FromBinaryBuffer/ubjson / citm_catalog` | 4.07 ms | 1.79 ms | 2.28× | 2.44× | - | `FromBinaryBuffer/ubjson / jeopardy` | 379 ms | 117 ms | 3.24× | 3.40× | - | `FromBinaryBuffer/ubjson / twitter` | 3.3 ms | 981 µs | 3.36× | 2.90× | - | `FromBinaryBuffer/ubjson_optimized / canada` | 8.89 ms | 2.43 ms | 3.66× | 3.67× | - | `FromBinaryBuffer/ubjson_optimized / twitter` | 3.3 ms | 975 µs | 3.38× | 2.88× | - | `FromBinaryFile/bjdata / twitter` | 10.9 ms | 1.95 ms | 5.61× | 5.89× | - | `FromBinaryFile/bson / twitter` | 10.7 ms | 1.85 ms | 5.80× | 6.21× | - | `FromBinaryFile/cbor / canada` | 14.9 ms | 4.49 ms | 3.32× | 3.68× | - | `FromBinaryFile/cbor / twitter` | 10.4 ms | 1.57 ms | 6.59× | 6.46× | - | `FromBinaryFile/ubjson / canada` | 14.3 ms | 6.43 ms | 2.23× | 2.50× | - | `FromBinaryFile/ubjson / twitter` | 10.9 ms | 1.97 ms | 5.54× | 5.90× | - | `FromBinaryShape/containers / bjdata` | 14.7 ms | 7.23 ms | 2.03× | 2.05× | - | `FromBinaryShape/containers / bson` | 17.6 ms | 7.89 ms | 2.23× | 2.20× | - | `FromBinaryShape/containers / cbor` | 14.6 ms | 4.16 ms | 3.52× | 3.49× | - | `FromBinaryShape/containers / msgpack` | 14.6 ms | 3.89 ms | 3.75× | 3.52× | - | `FromBinaryShape/containers / ubjson_optimized` | 15.5 ms | 4.56 ms | 3.40× | 3.34× | - | `FromBinaryShape/containers / ubjson` | 15 ms | 7.24 ms | 2.07× | 2.03× | - | `FromBinaryShape/nested / bjdata` | 96.8 µs | 38.3 µs | 2.53× | 2.50× | - | `FromBinaryShape/nested / bson` | 101 µs | 42.6 µs | 2.38× | 2.42× | - | `FromBinaryShape/nested / cbor` | 94.9 µs | 35.8 µs | 2.65× | 2.57× | - | `FromBinaryShape/nested / msgpack` | 92.6 µs | 35.1 µs | 2.64× | 2.60× | - | `FromBinaryShape/nested / ubjson` | 96.1 µs | 38.3 µs | 2.51× | 2.50× | - | `FromBinaryShape/scalars / bjdata` | 25.3 ms | 11.6 ms | 2.18× | 2.22× | - | `FromBinaryShape/scalars / cbor` | 26.2 ms | 5.89 ms | 4.45× | 3.56× | - | `FromBinaryShape/scalars / msgpack` | 25.1 ms | 5.65 ms | 4.44× | 3.69× | - | `FromBinaryShape/scalars / ubjson` | 25.3 ms | 11.6 ms | 2.18× | 2.03× | - | `FromCborChunkedString/10000 chunks` | 91.8 µs | 82.9 µs | 1.11× | 1.09× | - | `FromMsgpack/canada` | 14.9 ms | 4.56 ms | 3.26× | 3.63× | - | `FromMsgpack/citm_catalog` | 9.36 ms | 2.26 ms | 4.14× | 4.60× | - | `FromMsgpack/floats` | 61.3 ms | 28.7 ms | 2.14× | 2.89× | - | `FromMsgpack/jeopardy` | 1,170 ms | 190 ms | 6.14× | 6.80× | - | `FromMsgpack/signed_ints` | 63.9 ms | 32.9 ms | 1.94× | 2.44× | - | `FromMsgpack/small_signed_ints` | 64.5 ms | 34.4 ms | 1.87× | 2.33× | - | `FromMsgpack/twitter` | 10.3 ms | 1.49 ms | 6.92× | 6.76× | - | `FromMsgpack/unsigned_ints` | 57.8 ms | 28.3 ms | 2.04× | 2.77× | - | `GetArrayThe labeled steps of the timeline: the change in time at the first commit of each -pull request, against the commit before it. [#5592][5592] is by [@mvanslobbe](https://github.com/mvanslobbe).
+pull request, against the commit before it; for [#6031][6031], which was not merged yet, the step to 3.13.0. +[#5592][5592] is by [@mvanslobbe](https://github.com/mvanslobbe). ### D. Algorithmic fixes @@ -1931,8 +2039,8 @@ every round, in a different order each round (a shuffle with a fixed seed), each time is the best of 2 × 30 rounds (2 × 6 for `jeopardy.json`). Boost.JSON uses a monotonic resource and precise number parsing, RapidJSON `kParseFullPrecisionFlag` (except in *validate*, which reads no number), serde_json `float_roundtrip`: every number a library converts has to be exact. That rule, like the others here, is our choice. -Between the two runs, a library's time relative to `json_document`'s moved by 0.8% at the median and 2.5% at the 95th -percentile on the Mac, and by 2.5–3% and 9–10% on x86-64. +Between the two runs, a C++ library's time relative to `json_document`'s moved by 0.6% at the median and 2.4% at the +95th percentile on the Mac, and by 1.7–2.0% and 7–8% on x86-64 (the Rust programs were not run again). **Validation levels.** A way validates fully if it rejects all four broken inputs (cut short by one byte, a stray comma, an invalid UTF-8 byte in a string, garbage after the value); "U" accepts the invalid UTF-8 byte; "P" accepts one of the @@ -1988,96 +2096,96 @@ cell for the times and the way. | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | accept | 170 | 313 | 886 | 20,873 | 0.9 | - | yyjson | insitu (copy untimed) | 172 | 451 | 1,457 | 30,240 | 0.9 | - | simdjson | DOM | 212 | 452 | 1,888 | 28,843 | 1.1 | + | **nlohmann/json `json_document`** | accept | 172 | 313 | 905 | 20,926 | 0.9 | + | yyjson | insitu (copy), read | 183 | 476 | 1,487 | 30,922 | 0.9 | + | nlohmann/json `json` | accept | 234 | 456 | 1,253 | 27,563 | 1.1 | + | simdjson | DOM | 215 | 460 | 1,890 | 28,890 | 1.1 | | sonic-rs (Rust) | Value | 326 | 681 | 1,939 | 31,450 | 1.4 | - | Glaze | validate_json | 331 | 862 | 1,326 | 45,192 | 1.4 | + | Glaze | validate_json | 333 | 854 | 1,302 | 46,251 | 1.4 | | simd-json (Rust) | tape (copy) | 289 | 698 | 2,278 | 38,874 | 1.7 | - | sonic-cpp | Document [U] | – (U 382) | – (U 985) | – (U 2,578) | – (U 41,617) | – (U 1.6) | + | sonic-cpp | Document [U] | – (U 382) | – (U 985) | – (U 2,579) | – (U 40,996) | – (U 1.7) | | serde_json (Rust) | IgnoredAny [U], from_utf8 + IgnoredAny | 452 (U 354) | 1,103 (U 1,047) | 1,556 (U 1,451) | 46,517 (U 44,984) | 2.0 (U 1.7) | - | Boost.JSON | SAX, SAX (numbers: none) | 535 | 994 | 1,467 | 53,939 | 2.3 | - | nlohmann/json `json` | accept | 451 | 888 | 2,405 | 55,286 | 2.4 | - | RapidJSON | SAX (default precision), SAX (string stream, default precision), SAX (string stream, default precision) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,307 (U 686) | 1,397 (U 1,032) | 1,982 | 134,679 (U 63,589) | 6.0 (U 2.6) | + | Boost.JSON | SAX, SAX (numbers: none) | 540 | 990 | 1,449 | 54,480 | 2.4 | + | RapidJSON | SAX (default precision), SAX (string stream, default precision), SAX (string stream, default precision) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,318 (U 706) | 1,391 (U 1,033) | 1,971 | 133,695 (U 64,732) | 6.1 (U 2.7) | **sum** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse | 224 | 485 | 1,816 | 28,987 | 1.2 | - | yyjson | insitu (copy untimed) | 219 | 551 | 1,906 | 37,512 | 1.1 | - | simdjson | DOM, On-Demand | 249 | 571 | 2,374 | 35,111 | 1.3 | + | **nlohmann/json `json_document`** | parse | 219 | 493 | 1,803 | 28,898 | 1.2 | + | yyjson | insitu (copy), read | 231 | 571 | 1,875 | 37,817 | 1.1 | + | simdjson | DOM, On-Demand | 249 | 572 | 2,398 | 34,994 | 1.3 | + | nlohmann/json `json` | sax | 303 | 602 | 2,208 | 44,688 | 1.6 | | sonic-rs (Rust) | visitor | 364 | 778 | 2,703 | 41,361 | 1.7 | | simd-json (Rust) | visitor (copy) | 354 | 866 | 2,717 | 47,518 | 2.0 | - | sonic-cpp | Document [U] | – (U 428) | – (U 1,087) | – (U 2,993) | – (U 48,688) | – (U 1.9) | - | nlohmann/json `json` | sax | 465 | 925 | 3,072 | 64,089 | 2.4 | - | Boost.JSON | SAX | 535 | 1,010 | 3,067 | 53,704 | 2.3 | + | sonic-cpp | Document [U] | – (U 428) | – (U 1,098) | – (U 2,975) | – (U 48,487) | – (U 1.9) | + | Boost.JSON | SAX | 538 | 1,028 | 3,084 | 54,765 | 2.3 | | serde_json (Rust) | visitor | 652 | 1,242 | 4,772 | 66,568 | 2.5 | - | Glaze | generic (double) [P], generic (double, strict), lazy_json [P] | 968 | 2,049 | 6,331 | 99,763 | 4.9 (P 3.5) | - | RapidJSON | SAX, SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy untimed) (no encoding check) [U] | 1,329 (U 735) | 1,455 (U 1,136) | 6,453 | 136,103 (U 69,604) | 6.1 (U 2.9) | + | Glaze | generic (double) [P], generic (double, strict), lazy_json [P] | 974 | 2,057 | 6,338 | 100,272 | 4.9 (P 3.5) | + | RapidJSON | SAX, SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy) (no encoding check) [U] | 1,332 (U 738) | 1,472 (U 1,157) | 6,448 | 136,330 (U 71,646) | 6.2 (U 2.9) | **select** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse | 184 | 363 | 956 | 24,991 | 1.0 | - | yyjson | insitu (copy untimed) | 178 | 456 | 1,461 | 32,514 | 1.0 | - | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P], On-Demand structs [P] | 196 (P 127) | 450 (P 354) | 1,886 (P 566) | 30,504 (P 19,562) | 1.2 (P 0.8) | - | Glaze | structs (validate_skipped), structs [P] | 311 (P 241) | 854 (P 255) | 1,316 (P 313) | 46,924 (P 37,578) | 1.4 (P 1.2) | + | **nlohmann/json `json_document`** | parse | 183 | 363 | 944 | 24,801 | 1.0 | + | yyjson | insitu (copy), read, read (raw numbers) | 191 | 480 | 1,489 | 32,777 | 1.1 | + | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P], On-Demand structs [P] | 192 (P 128) | 454 (P 356) | 1,893 (P 571) | 30,714 (P 19,633) | 1.2 (P 0.8) | + | Glaze | structs (validate_skipped), structs [P] | 314 (P 238) | 852 (P 253) | 1,303 (P 310) | 47,692 (P 36,013) | 1.4 (P 1.2) | | sonic-rs (Rust) | Value | 333 | 691 | 1,936 | 40,660 | 1.7 | - | sonic-cpp | Document [U] | – (U 385) | – (U 990) | – (U 2,586) | – (U 45,858) | – (U 1.7) | + | sonic-cpp | Document [U] | – (U 385) | – (U 993) | – (U 2,611) | – (U 46,356) | – (U 1.7) | | simd-json (Rust) | structs (copy) | 364 | 914 | 2,657 | 57,697 | 2.1 | | serde_json (Rust) | from_utf8 + structs, structs [U] | 480 (U 410) | 1,125 (U 1,084) | 1,560 (U 1,455) | 62,378 | 2.3 (U 1.9) | - | Boost.JSON | parse, parse_into structs | 617 | 1,205 | 3,650 | 67,255 | 2.7 | - | RapidJSON | Document (string stream), insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,304 (U 679) | 1,554 (U 1,126) | 6,776 | 141,509 (U 65,625) | 6.5 (U 2.6) | - | nlohmann/json `json` | parse, structs (from_json) | 1,360 | 2,416 | 6,047 | 179,533 | 7.6 | + | Boost.JSON | parse, parse_into structs | 623 | 1,208 | 3,638 | 67,923 | 2.7 | + | nlohmann/json `json` | parse | 1,208 | 2,128 | 5,088 | 163,712 | 6.6 | + | RapidJSON | Document (string stream), Document (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,314 (U 701) | 1,578 (U 1,166) | 6,814 | 144,649 (U 67,617) | 6.6 (U 2.8) | **edit** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 291 | 560 | 2,765 (N 1,484) | 58,057 | 1.9 | - | yyjson | insitu (copy untimed) + mut copy | 427 | 882 | 3,642 | 84,239 | 2.4 | - | sonic-cpp | Document [U] | – (U 486) | – (U 1,224) | – (U 5,638) | – (U 62,688) | – (U 2.6) | + | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 288 | 559 | 2,737 (N 1,504) | 58,019 | 1.9 | + | yyjson | insitu (copy) + mut copy, mut copy | 440 | 904 | 3,692 | 84,466 | 2.4 | + | sonic-cpp | Document [U] | – (U 488) | – (U 1,218) | – (U 5,662) | – (U 63,697) | – (U 2.6) | | sonic-rs (Rust) | Value | 634 | 1,257 | 4,396 | 140,936 | 3.8 | | simd-json (Rust) | borrowed (copy) | 948 | 2,102 | 7,654 | 110,693 | 5.4 | - | Boost.JSON | parse | 1,368 | 1,992 | 6,573 | 155,237 | 6.6 | - | nlohmann/json `json` | ordered_json, parse | 1,273 | 2,344 | 8,334 | 203,232 | 7.1 | - | Glaze | generic (strict), generic [P] | 1,539 | 3,235 | 11,083 | 178,333 | 8.3 | - | RapidJSON | Document, Document (string stream), insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,922 (U 1,344) | 2,388 (U 1,964) | 13,836 | 221,270 (U 144,741) | 10.3 (U 6.5) | + | nlohmann/json `json` | ordered_json, parse | 1,115 | 2,065 | 7,348 | 162,216 | 6.3 | + | Boost.JSON | parse | 1,355 | 1,997 | 6,522 | 157,849 | 6.6 | + | Glaze | generic (strict), generic [P] | 1,527 | 3,216 | 11,033 | 183,648 | 8.4 | + | RapidJSON | Document, Document (string stream), insitu (copy), insitu (copy) (no encoding check) [U] | 1,924 (U 1,356) | 2,393 (U 1,992) | 13,711 | 224,328 (U 147,902) | 10.5 (U 6.5) | | serde_json (Rust) | Value | 2,009 | 3,316 | 9,666 | 251,931 | 9.6 | **minify** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N] | 259 | 493 | 2,582 (N 1,360) | 37,882 | 1.4 | - | yyjson | insitu (copy untimed) + write, read (raw numbers) + write [N] | 279 | 625 | 3,196 (N 2,550) | 50,821 | 1.6 | - | simdjson | DOM + to_string, minify [P] | 423 (P 62.3) | 848 (P 171) | 5,120 (P 227) | 61,059 (P 5,762) | 2.4 (P 0.3) | + | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N] | 261 (N 252) | 496 | 2,570 (N 1,343) | 37,624 | 1.5 | + | yyjson | insitu (copy) + write, read (raw numbers) + write [N], read + write | 298 | 642 | 3,222 (N 2,523) | 51,001 | 1.6 | + | simdjson | DOM + to_string, minify [P] | 425 (P 62.5) | 850 (P 168) | 5,094 (P 227) | 61,202 (P 5,756) | 2.3 (P 0.3) | | sonic-rs (Rust) | Value | 535 | 1,134 | 4,377 | 64,832 | 3.0 | - | sonic-cpp | Document + Serialize [U] | – (U 500) | – (U 1,220) | – (U 5,676) | – (U 61,236) | – (U 2.5) | + | sonic-cpp | Document + Serialize [U] | – (U 504) | – (U 1,217) | – (U 5,634) | – (U 61,988) | – (U 2.5) | | simd-json (Rust) | borrowed (copy) | 952 | 2,111 | 7,658 | 108,130 | 5.5 | - | Boost.JSON | parse + serialize | 1,393 | 1,986 | 6,542 | 154,677 | 6.7 | - | RapidJSON | Document + Writer, SAX to Writer, SAX to Writer (raw numbers) (no encoding check) [U N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 1,525 (U 1,058) | 1,808 (U 1,485) | 14,460 (U N 10,908) | 174,558 (U 123,119) | 8.1 (U 5.0) | - | nlohmann/json `json` | parse + dump | 1,600 | 2,706 | 8,389 | 197,745 | 8.8 | - | Glaze | generic (strict), minify_json [P] | 1,505 (P 281) | 3,225 (P 729) | 11,092 (P 1,722) | 168,855 (P 39,976) | 8.1 (P 1.3) | + | Boost.JSON | parse + serialize | 1,407 | 2,003 | 6,555 | 154,865 | 6.8 | + | nlohmann/json `json` | parse + dump | 1,446 | 2,406 | 7,328 | 182,132 | 7.7 | + | RapidJSON | Document + Writer, SAX to Writer, SAX to Writer (raw numbers) [N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 1,523 (U 1,044) | 1,803 (U 1,499) | 14,378 (N 11,568) | 174,958 (U 123,908) | 8.0 (U 4.9) | + | Glaze | generic (strict), minify_json [P] | 1,512 (P 284) | 3,245 (P 747) | 11,112 (P 1,746) | 172,173 (P 40,439) | 8.1 (P 1.4) | | serde_json (Rust) | Value | 2,004 | 3,290 | 9,792 | 227,369 | 9.5 | **write** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 74.3 | 134 | 1,621 (N 401) | 14,757 | 0.5 | - | yyjson | write | 103 | 169 | 1,731 | 20,087 | 0.7 | - | sonic-cpp | Serialize | 108 | 230 | 3,093 | 19,651 | 0.8 | - | nlohmann/json `json` | dump | 223 | 287 | 2,276 | 31,825 | 1.2 | - | simdjson | to_string | 199 | 385 | 3,241 | 32,208 | 1.3 | - | Glaze | generic, generic (strict) | 216 | 469 | 1,720 | 33,934 | 1.3 | + | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 76.2 (N 72.8) | 139 | 1,625 (N 398) | 14,861 | 0.5 | + | yyjson | write | 101 | 167 | 1,719 | 19,937 | 0.7 | + | sonic-cpp | Serialize | 114 | 240 | 3,109 | 20,327 | 0.9 | + | nlohmann/json `json` | dump | 216 | 287 | 2,274 | 30,928 | 1.2 | + | Glaze | generic, generic (strict) | 212 | 476 | 1,702 | 34,032 | 1.3 | + | simdjson | to_string | 201 | 416 | 3,186 | 32,185 | 1.3 | | sonic-rs (Rust) | Value | 212 | 446 | 2,419 | 33,366 | 1.5 | | serde_json (Rust) | Value | 322 | 490 | 2,286 | 49,967 | 2.0 | | simd-json (Rust) | borrowed | 355 | 478 | 2,728 | 47,412 | 1.8 | - | Boost.JSON | serialize | 507 | 508 | 2,923 | 62,583 | 2.9 | - | RapidJSON | Writer | 603 | 830 | 6,995 | 81,150 | 3.9 | + | Boost.JSON | serialize | 499 | 511 | 2,873 | 63,201 | 2.9 | + | RapidJSON | Writer | 596 | 836 | 6,928 | 80,160 | 4.0 | ??? info "Times in µs on x86-64 with GCC 16.2" @@ -2085,96 +2193,96 @@ cell for the times and the way. | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | accept | 258 | 513 | 934 | 34,514 | 1.2 | - | yyjson | insitu (copy untimed), read (raw numbers) | 352 | 837 | 2,199 | 93,043 | 1.5 | + | **nlohmann/json `json_document`** | accept | 254 | 515 | 892 | 33,006 | 1.2 | + | nlohmann/json `json` | accept | 370 | 883 | 2,441 | 39,142 | 1.3 | | sonic-rs (Rust) | IgnoredAny | 501 | 970 | 2,249 | 54,369 | 1.8 | - | Boost.JSON | SAX, SAX (numbers: none) | 690 | 917 | 1,676 | 62,781 | 2.9 | - | simdjson | DOM | 301 | 695 | 3,337 | 120,982 | 1.7 | - | sonic-cpp | Document [U] | – (U 403) | – (U 932) | – (U 3,349) | – (U 98,511) | – (U 1.7) | + | Boost.JSON | SAX, SAX (numbers: none) | 683 | 913 | 1,642 | 57,932 | 2.9 | + | yyjson | insitu (copy), read, read (raw numbers) | 393 | 930 | 2,140 | 122,230 | 1.6 | + | simdjson | DOM | 297 | 687 | 3,165 | 111,631 | 1.7 | + | sonic-cpp | Document [U] | – (U 404) | – (U 923) | – (U 3,216) | – (U 93,142) | – (U 1.7) | | serde_json (Rust) | IgnoredAny [U], from_utf8 + IgnoredAny | 624 (U 484) | 1,440 (U 1,370) | 2,406 (U 2,314) | 64,720 (U 58,247) | 2.4 (U 1.9) | - | Glaze | validate_json | 712 | 1,983 | 2,625 | 76,759 | 2.5 | - | nlohmann/json `json` | accept | 683 | 1,419 | 4,559 | 74,331 | 3.1 | + | Glaze | validate_json | 705 | 1,971 | 2,532 | 74,805 | 2.5 | | simd-json (Rust) | tape (copy) | 567 | 1,430 | 4,359 | 207,551 | 2.9 | - | RapidJSON | SAX (default precision) (no encoding check) [U], SAX (string stream, default precision), SAX (string stream, default precision) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,166 (U 1,012) | 1,553 (U 1,434) | 3,875 | 114,704 (U 102,635) | 4.8 (U 3.2) | + | RapidJSON | SAX (default precision) (no encoding check) [U], SAX (string stream) (no encoding check) [U], SAX (string stream, default precision), insitu (copy), insitu (copy) (no encoding check) [U] | 1,215 (U 1,052) | 1,550 (U 1,427) | 3,625 | 125,139 (U 104,342) | 4.9 (U 3.2) | **sum** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | simdjson | On-Demand | 348 | 805 | 3,452 | 92,769 | 1.9 | - | **nlohmann/json `json_document`** | parse | 398 | 972 | 3,501 | 89,756 | 2.0 | - | yyjson | insitu (copy untimed) | 415 | 986 | 3,818 | 97,586 | 1.9 | + | simdjson | On-Demand | 344 | 809 | 3,405 | 90,960 | 1.9 | + | **nlohmann/json `json_document`** | parse | 384 | 1,066 | 3,526 | 88,527 | 1.9 | + | nlohmann/json `json` | sax | 533 | 1,113 | 4,134 | 66,403 | 2.3 | | sonic-rs (Rust) | visitor | 501 | 1,123 | 4,397 | 59,583 | 2.5 | - | sonic-cpp | Document [U] | – (U 456) | – (U 1,115) | – (U 3,980) | – (U 105,081) | – (U 2.0) | - | Boost.JSON | SAX | 699 | 957 | 5,835 | 59,656 | 3.0 | - | nlohmann/json `json` | sax | 814 | 1,666 | 5,690 | 97,414 | 4.0 | + | sonic-cpp | Document [U] | – (U 458) | – (U 1,120) | – (U 3,887) | – (U 107,494) | – (U 2.0) | + | yyjson | insitu (copy), read | 445 | 1,093 | 3,948 | 133,860 | 1.9 | + | Boost.JSON | SAX | 690 | 956 | 5,762 | 62,051 | 2.9 | | simd-json (Rust) | visitor (copy) | 681 | 1,650 | 5,083 | 210,185 | 3.5 | - | RapidJSON | SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,221 (U 1,063) | 1,717 (U 1,635) | 9,435 | 112,158 (U 105,845) | 5.1 (U 3.5) | | serde_json (Rust) | visitor | 1,311 | 2,288 | 7,127 | 129,497 | 4.6 | - | Glaze | generic (double) [P], generic (double, strict), lazy_json [P] | 2,483 | 4,413 | 10,532 | 231,505 | 8.5 (P 6.4) | + | RapidJSON | SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,256 (U 1,099) | 1,713 (U 1,635) | 9,547 (U 9,105) | 127,564 (U 113,500) | 5.1 (U 3.4) | + | Glaze | generic (double) [P], generic (double, strict), lazy_json [P] | 2,317 | 4,068 | 10,333 | 237,723 | 8.3 (P 6.3) | **select** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse, structs (from_json) | 333 | 828 | 1,368 | 89,876 | 1.7 | - | yyjson | insitu (copy untimed), read (raw numbers) | 366 | 840 | 2,204 | 96,172 | 1.7 | + | **nlohmann/json `json_document`** | parse | 325 | 863 | 1,302 | 87,940 | 1.6 | + | yyjson | insitu (copy), read, read (raw numbers) | 402 | 954 | 2,127 | 140,491 | 1.7 | | sonic-rs (Rust) | structs | 504 | 1,033 | 2,328 | 90,486 | 2.0 | - | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P] | 313 (P 180) | 699 (P 496) | 3,325 (P 690) | 122,556 (P 48,272) | 1.9 (P 1.1) | - | sonic-cpp | Document [U] | – (U 407) | – (U 948) | – (U 3,464) | – (U 110,885) | – (U 1.8) | + | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P] | 311 (P 178) | 698 (P 494) | 3,141 (P 674) | 130,228 (P 48,492) | 1.9 (P 1.1) | + | sonic-cpp | Document [U] | – (U 408) | – (U 942) | – (U 3,166) | – (U 110,763) | – (U 1.8) | + | Glaze | structs (validate_skipped), structs [P] | 608 (P 345) | 1,978 (P 401) | 2,521 (P 415) | 90,807 (P 67,456) | 2.1 (P 1.5) | | serde_json (Rust) | from_utf8 + structs, structs [U] | 701 | 1,532 | 2,529 (U 2,402) | 107,316 | 3.0 | - | Glaze | structs (validate_skipped), structs [P] | 606 (P 344) | 2,476 (P 402) | 2,557 (P 417) | 89,957 (P 67,467) | 2.1 (P 1.5) | + | Boost.JSON | parse, parse_into structs | 883 | 1,286 | 6,690 | 101,286 | 3.6 | | simd-json (Rust) | structs (copy) | 679 | 1,731 | 5,144 | 239,164 | 3.5 | - | Boost.JSON | parse, parse_into structs | 887 | 1,270 | 7,292 | 95,866 | 3.7 | - | RapidJSON | Document (string stream), insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,180 (U 1,022) | 1,693 | 10,390 | 116,394 (U 108,290) | 4.9 (U 3.4) | - | nlohmann/json `json` | parse, structs (from_json) | 3,360 | 5,268 | 12,006 | 410,221 | 17.7 | + | RapidJSON | Document (string stream), Document (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,217 (U 1,056) | 1,807 | 9,688 | 152,275 | 5.0 (U 3.5) | + | nlohmann/json `json` | parse, structs (from_json) | 2,984 | 4,548 | 9,124 | 383,108 | 15.2 | **edit** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 536 | 1,162 | 5,567 (N 2,188) | 192,323 | 3.0 | - | sonic-cpp | Document [U] | – (U 620) | – (U 1,438) | – (U 8,495) | – (U 186,078) | – (U 3.4) | - | yyjson | insitu (copy untimed) + mut copy | 746 | 1,526 | 7,479 | 275,025 | 3.6 | + | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 538 | 1,214 | 5,640 (N 2,140) | 193,024 | 3.1 | + | sonic-cpp | Document [U] | – (U 620) | – (U 1,426) | – (U 8,466) | – (U 181,035) | – (U 3.3) | + | yyjson | insitu (copy) + mut copy, mut copy | 786 | 1,642 | 7,599 | 303,303 | 3.8 | | sonic-rs (Rust) | Value | 1,197 | 2,389 | 7,744 | 390,952 | 6.9 | - | Boost.JSON | parse | 1,767 | 2,493 | 12,767 | 331,310 | 8.5 | - | RapidJSON | Document (string stream), insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,865 (U 1,737) | 2,616 | 18,812 | 245,724 (U 234,669) | 8.7 (U 7.2) | + | Boost.JSON | parse | 1,859 | 2,513 | 12,765 | 348,703 | 8.8 | + | RapidJSON | Document (string stream), Document (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,896 (U 1,749) | 2,754 | 19,359 | 269,071 (U 259,148) | 9.1 (U 7.5) | | simd-json (Rust) | borrowed (copy), owned (copy) | 1,862 | 4,194 | 14,896 | 391,369 | 9.0 | - | nlohmann/json `json` | ordered_json | 2,832 | 4,970 | 15,806 | 647,704 | 13.1 | - | Glaze | generic (strict), generic [P] | 3,389 | 6,002 | 21,598 | 607,767 | 14.8 | + | nlohmann/json `json` | ordered_json, parse | 2,535 | 4,484 | 13,526 | 635,912 | 12.4 | + | Glaze | generic (strict), generic [P] | 3,364 | 6,036 | 21,592 | 652,302 (P 631,009) | 15.6 | | serde_json (Rust) | Value | 4,042 | 6,586 | 16,631 | 632,766 | 17.6 | **minify** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N] | 518 (N 496) | 1,045 | 5,240 (N 2,196) | 143,737 | 2.5 (N 2.4) | - | yyjson | insitu (copy untimed) + write, insitu (copy) + write, read (raw numbers) + write [N] | 626 | 1,160 | 6,701 (N 2,901) | 156,605 | 2.8 | - | sonic-cpp | Document + Serialize [U] | – (U 675) | – (U 1,417) | – (U 8,958) | – (U 164,630) | – (U 3.3) | - | simdjson | DOM + to_string, minify [P] | 714 (P 99.9) | 1,318 (P 278) | 9,135 (P 312) | 269,597 (P 42,644) | 3.7 (P 0.5) | + | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N] | 478 | 1,152 | 5,209 (N 2,150) | 152,631 (N 145,664) | 2.6 (N 2.4) | + | yyjson | insitu (copy) + write, read (raw numbers) + write [N], read + write | 610 | 1,277 (N 1,221) | 6,630 (N 2,860) | 199,086 | 3.0 | + | sonic-cpp | Document + Serialize [U] | – (U 636) | – (U 1,498) | – (U 8,354) | – (U 173,180) | – (U 3.3) | + | simdjson | DOM + to_string, minify [P] | 666 (P 93.3) | 1,354 (P 279) | 8,606 (P 314) | 280,739 (P 46,006) | 3.9 (P 0.5) | | sonic-rs (Rust) | Value | 1,000 | 2,097 | 7,795 | 215,280 | 5.4 | - | Boost.JSON | parse + serialize | 1,974 | 2,525 | 12,748 | 314,299 | 8.6 | - | RapidJSON | SAX to Writer (raw numbers) [N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 1,949 (U 1,880) | 2,382 | 19,548 (N 14,400) | 231,979 (U 224,249) | 8.2 (U 6.2) | + | Boost.JSON | parse + serialize | 1,783 | 2,498 | 12,367 | 329,300 | 8.9 | + | RapidJSON | SAX to Writer (raw numbers) [N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 1,851 (U 1,710) | 2,429 | 18,810 (N 14,205) | 229,769 | 9.0 (U 6.7) | | simd-json (Rust) | borrowed (copy) | 1,889 | 4,196 | 15,716 | 414,377 | 9.2 | - | Glaze | generic (strict), minify_json [P] | 3,685 (P 442) | 5,942 (P 1,292) | 22,138 (P 2,726) | 450,322 (P 97,377) | 14.0 (P 1.9) | + | nlohmann/json `json` | parse + dump | 3,369 | 5,399 | 13,402 | 493,302 | 18.4 | + | Glaze | generic (strict), minify_json [P] | 3,243 (P 417) | 6,175 (P 1,299) | 20,961 (P 2,699) | 451,368 (P 93,794) | 15.3 (P 2.1) | | serde_json (Rust) | Value | 4,012 | 6,583 | 17,306 | 509,702 | 16.5 | - | nlohmann/json `json` | parse + dump | 4,196 | 5,806 | 16,626 | 529,560 | 19.2 | **write** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 156 (N 140) | 251 (N 230) | 3,842 (N 823) | 67,769 (N 65,376) | 0.9 (N 0.8) | - | yyjson | write | 215 | 319 | 3,360 | 66,669 | 1.3 | - | sonic-cpp | Serialize | 249 | 509 | 5,074 | 69,916 | 1.6 | - | nlohmann/json `json` | dump | 444 | 640 | 4,089 | 117,316 | 1.8 | - | simdjson | to_string | 361 | 614 | 5,609 | 158,492 | 1.8 | - | Glaze | generic | 381 | 730 | 4,071 | 131,946 | 2.0 | + | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 154 (N 139) | 251 (N 229) | 3,946 (N 831) | 64,776 | 0.9 (N 0.8) | + | yyjson | write | 185 | 318 | 3,330 | 67,531 | 1.3 | + | sonic-cpp | Serialize | 258 | 508 | 5,077 | 72,262 | 1.6 | + | nlohmann/json `json` | dump | 413 | 611 | 3,905 | 113,990 | 1.8 | + | simdjson | to_string | 346 | 610 | 5,580 | 158,670 | 1.8 | + | Glaze | generic | 377 | 730 | 4,050 | 129,720 | 2.0 | | sonic-rs (Rust) | Value | 434 | 927 | 3,987 | 109,176 | 2.8 | - | Boost.JSON | serialize | 469 | 830 | 5,659 | 144,174 | 2.4 | + | Boost.JSON | serialize | 451 | 832 | 5,570 | 138,780 | 2.4 | | simd-json (Rust) | borrowed | 582 | 800 | 5,147 | 122,623 | 2.8 | | serde_json (Rust) | Value | 550 | 887 | 4,005 | 134,163 | 3.3 | - | RapidJSON | Writer | 678 | 979 | 9,095 | 131,408 | 3.8 | + | RapidJSON | Writer | 638 | 984 | 8,886 | 131,435 | 3.9 | ??? info "Times in µs on x86-64 with GCC 13.4" @@ -2182,96 +2290,96 @@ cell for the times and the way. | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | accept | 271 | 609 | 1,011 | 34,722 | 1.2 | + | **nlohmann/json `json_document`** | accept | 267 | 664 | 986 | 34,874 | 1.2 | + | nlohmann/json `json` | accept | 312 | 686 | 2,345 | 38,948 | 1.3 | | sonic-rs (Rust) | IgnoredAny | 501 | 970 | 2,249 | 54,369 | 1.8 | - | yyjson | insitu (copy untimed), read (raw numbers) | 348 | 878 | 2,256 | 90,959 | 1.6 | - | Boost.JSON | SAX, SAX (numbers: none) | 710 | 969 | 1,718 | 61,476 | 3.2 | - | simdjson | DOM | 311 | 780 | 3,351 | 116,704 | 1.8 | - | Glaze | validate_json | 590 | 1,545 | 1,980 | 76,009 | 2.3 | - | sonic-cpp | Document [U] | – (U 408) | – (U 1,028) | – (U 3,622) | – (U 96,080) | – (U 1.6) | + | Glaze | validate_json | 584 | 1,402 | 1,837 | 71,382 | 2.3 | + | Boost.JSON | SAX, SAX (numbers: none) | 730 | 934 | 1,637 | 60,745 | 3.2 | + | yyjson | insitu (copy), read, read (raw numbers) | 394 | 984 | 2,222 | 128,528 | 1.6 | + | simdjson | DOM | 309 | 737 | 3,183 | 115,296 | 1.8 | + | sonic-cpp | Document [U] | – (U 409) | – (U 1,025) | – (U 3,561) | – (U 96,607) | – (U 1.5) | | serde_json (Rust) | IgnoredAny [U], from_utf8 + IgnoredAny | 624 (U 484) | 1,440 (U 1,370) | 2,406 (U 2,314) | 64,720 (U 58,247) | 2.4 (U 1.9) | - | nlohmann/json `json` | accept | 677 | 1,433 | 4,584 | 73,561 | 3.3 | | simd-json (Rust) | tape (copy) | 567 | 1,430 | 4,359 | 207,551 | 2.9 | - | RapidJSON | SAX (default precision) (no encoding check) [U], SAX (string stream, default precision), insitu (copy untimed), insitu (copy untimed) (no encoding check) [U], insitu (copy) (no encoding check) [U] | 1,552 (U 1,082) | 2,210 (U 1,570) | 4,104 | 168,747 (U 110,856) | 6.9 (U 3.7) | + | RapidJSON | SAX (default precision) (no encoding check) [U], SAX (string stream) (no encoding check) [U], SAX (string stream, default precision), SAX (string stream, default precision) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,201 (U 1,033) | 1,931 (U 1,487) | 4,114 (U 3,966) | 145,137 (U 127,587) | 4.8 (U 3.2) | **sum** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | simdjson | On-Demand | 356 | 812 | 3,435 | 96,250 | 2.0 | - | **nlohmann/json `json_document`** | parse | 392 | 946 | 3,888 | 89,044 | 2.0 | + | simdjson | On-Demand | 356 | 819 | 3,291 | 94,267 | 1.9 | + | **nlohmann/json `json_document`** | parse | 387 | 1,036 | 3,704 | 89,477 | 1.9 | + | nlohmann/json `json` | sax | 451 | 941 | 3,816 | 64,406 | 2.3 | | sonic-rs (Rust) | visitor | 501 | 1,123 | 4,397 | 59,583 | 2.5 | - | sonic-cpp | Document [U] | – (U 460) | – (U 1,210) | – (U 4,295) | – (U 107,092) | – (U 1.9) | - | yyjson | insitu (copy untimed) | 407 | 1,045 | 6,021 | 99,764 | 2.0 | - | Boost.JSON | SAX | 734 | 1,030 | 6,103 | 64,382 | 3.3 | - | nlohmann/json `json` | sax | 797 | 1,548 | 6,004 | 101,516 | 4.0 | + | sonic-cpp | Document [U] | – (U 459) | – (U 1,209) | – (U 4,247) | – (U 103,731) | – (U 1.9) | + | yyjson | insitu (copy), read | 443 | 1,146 | 5,974 | 129,054 | 2.0 | + | Boost.JSON | SAX | 746 | 1,025 | 5,370 | 63,333 | 3.3 | | simd-json (Rust) | visitor (copy) | 681 | 1,650 | 5,083 | 210,185 | 3.5 | | serde_json (Rust) | visitor | 1,311 | 2,288 | 7,127 | 129,497 | 4.6 | - | RapidJSON | SAX (no encoding check) [U], SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,616 (U 1,130) | 2,290 (U 1,743) | 11,183 | 181,500 (U 119,706) | 7.5 (U 3.9) | - | Glaze | generic (double) [P], generic (double, strict), lazy_json [P] | 2,345 | 4,080 | 10,576 | 250,685 (P 231,181) | 9.0 (P 6.4) | + | RapidJSON | SAX (no encoding check) [U], SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,256 (U 1,085) | 2,114 (U 1,750) | 11,059 | 155,532 (U 128,734) | 5.2 (U 3.5) | + | Glaze | generic (double, strict), lazy_json [P] | 2,341 | 4,037 | 10,434 | 249,828 (P 219,119) | 8.5 (P 5.9) | **select** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse | 332 | 765 | 1,390 | 88,202 | 1.6 | - | yyjson | insitu (copy untimed), read (raw numbers) | 361 | 880 | 2,236 | 98,637 | 1.7 | + | **nlohmann/json `json_document`** | parse, structs (from_json) | 329 | 855 | 1,360 | 90,597 | 1.6 | + | yyjson | insitu (copy), read, read (raw numbers) | 397 | 994 | 2,228 | 140,073 | 1.8 | | sonic-rs (Rust) | structs | 504 | 1,033 | 2,328 | 90,486 | 2.0 | - | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P] | 324 (P 184) | 756 (P 509) | 3,409 (P 700) | 123,214 (P 48,614) | 2.0 (P 1.2) | - | Glaze | structs (validate_skipped), structs [P] | 529 (P 342) | 1,435 (P 410) | 1,913 (P 431) | 91,253 (P 66,069) | 2.0 (P 1.5) | - | sonic-cpp | Document [U] | – (U 413) | – (U 1,043) | – (U 3,695) | – (U 108,297) | – (U 1.7) | + | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P] | 323 (P 186) | 748 (P 508) | 3,189 (P 707) | 126,288 (P 50,215) | 2.0 (P 1.2) | + | Glaze | structs (validate_skipped), structs [P] | 523 (P 338) | 1,473 (P 406) | 1,853 (P 434) | 89,506 (P 66,029) | 1.9 (P 1.4) | + | sonic-cpp | Document [U] | – (U 412) | – (U 1,044) | – (U 3,632) | – (U 106,188) | – (U 1.7) | | serde_json (Rust) | from_utf8 + structs, structs [U] | 701 | 1,532 | 2,529 (U 2,402) | 107,316 | 3.0 | | simd-json (Rust) | structs (copy) | 679 | 1,731 | 5,144 | 239,164 | 3.5 | - | Boost.JSON | parse, parse_into structs | 884 | 1,380 | 7,217 | 99,078 | 3.9 | - | RapidJSON | Document (string stream) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,560 (U 1,079) | 2,127 (U 1,817) | 11,547 | 176,066 (U 122,354) | 7.1 (U 3.7) | - | nlohmann/json `json` | structs (from_json) | 3,325 | 5,126 | 11,908 | 423,362 | 17.0 | + | Boost.JSON | parse, parse_into structs | 899 | 1,353 | 7,011 | 100,039 | 3.9 | + | RapidJSON | Document (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,207 (U 1,037) | 1,947 (U 1,847) | 11,910 | 164,386 (U 147,489) | 5.0 (U 3.4) | + | nlohmann/json `json` | parse, structs (from_json) | 3,032 | 4,466 | 9,085 | 376,812 | 15.7 | **edit** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 532 | 1,116 | 5,714 (N 2,702) | 192,363 | 3.1 | - | sonic-cpp | Document [U] | – (U 637) | – (U 1,559) | – (U 8,936) | – (U 186,232) | – (U 3.3) | - | yyjson | insitu (copy untimed) + mut copy | 761 | 1,590 | 9,860 | 271,227 | 3.7 | + | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 530 | 1,209 | 5,792 (N 2,621) | 193,925 | 3.1 | + | sonic-cpp | Document [U] | – (U 632) | – (U 1,583) | – (U 8,734) | – (U 184,592) | – (U 3.4) | + | yyjson | insitu (copy) + mut copy, mut copy | 841 | 1,719 | 9,803 | 309,935 | 3.8 | | sonic-rs (Rust) | Value | 1,197 | 2,389 | 7,744 | 390,952 | 6.9 | - | Boost.JSON | parse | 1,963 | 2,936 | 13,414 | 356,244 | 9.1 | + | Boost.JSON | parse | 1,889 | 2,952 | 13,537 | 346,542 | 9.6 | + | RapidJSON | Document (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,920 (U 1,747) | 2,977 (U 2,823) | 21,152 | 293,851 (U 280,165) | 9.3 (U 7.6) | | simd-json (Rust) | borrowed (copy), owned (copy) | 1,862 | 4,194 | 14,896 | 391,369 | 9.0 | - | RapidJSON | Document (string stream) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U], insitu (copy) | 2,333 (U 1,838) | 3,133 (U 2,836) | 20,518 | 315,892 (U 271,888) | 11.0 (U 7.7) | - | nlohmann/json `json` | ordered_json | 2,912 | 4,999 | 15,603 | 604,744 | 13.5 | - | Glaze | generic (strict), generic [P] | 3,525 | 6,551 | 23,644 | 661,941 (P 632,543) | 15.6 | + | nlohmann/json `json` | ordered_json, parse | 2,503 | 4,414 | 13,479 | 622,816 | 12.7 | + | Glaze | generic (strict), generic [P] | 3,474 | 6,531 (P 6,302) | 23,193 | 673,526 | 16.5 (P 16.0) | | serde_json (Rust) | Value | 4,042 | 6,586 | 16,631 | 632,766 | 17.6 | **minify** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N], parse_copy + dump | 483 | 1,014 | 5,459 (N 2,620) | 146,320 | 2.4 | - | yyjson | insitu (copy untimed) + write, read (raw numbers) + write [N], read + write | 592 | 1,205 | 9,008 (N 3,106) | 158,577 | 2.9 | - | sonic-cpp | Document + Serialize [U] | – (U 695) | – (U 1,541) | – (U 8,838) | – (U 168,901) | – (U 3.3) | - | simdjson | DOM + to_string, minify [P] | 742 (P 123) | 1,393 (P 337) | 8,808 (P 433) | 266,718 (P 47,040) | 3.8 (P 0.6) | + | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N] | 475 | 1,102 | 5,414 (N 2,372) | 144,877 | 2.5 | + | yyjson | insitu (copy) + write, read (raw numbers) + write [N], read + write | 600 | 1,316 (N 1,264) | 9,151 (N 2,994) | 201,645 | 3.1 | + | sonic-cpp | Document + Serialize [U] | – (U 647) | – (U 1,563) | – (U 8,917) | – (U 176,625) | – (U 3.4) | + | simdjson | DOM + to_string, minify [P] | 689 (P 122) | 1,394 (P 336) | 8,457 (P 426) | 292,576 (P 49,731) | 4.1 (P 0.6) | | sonic-rs (Rust) | Value | 1,000 | 2,097 | 7,795 | 215,280 | 5.4 | - | Boost.JSON | parse + serialize | 2,086 | 2,919 | 13,974 | 334,468 | 9.2 | + | Boost.JSON | parse + serialize | 1,907 | 2,950 | 13,662 | 342,183 | 10.2 | | simd-json (Rust) | borrowed (copy) | 1,889 | 4,196 | 15,716 | 414,377 | 9.2 | - | RapidJSON | Document + Writer, SAX to Writer, SAX to Writer (no encoding check) [U], SAX to Writer (raw numbers) (no encoding check) [U N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 3,015 (U 1,978) | 3,012 (U 2,493) | 21,177 (U N 15,512) | 355,727 (U 249,235) | 13.9 (U 7.5) | - | Glaze | generic (strict), minify_json [P] | 3,780 (P 422) | 6,416 (P 1,144) | 23,765 (P 2,350) | 458,311 (P 96,405) | 14.7 (P 1.9) | + | RapidJSON | SAX to Writer, SAX to Writer (raw numbers) (no encoding check) [U N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 2,746 (U 1,824) | 3,162 (U 2,672) | 21,674 (U N 15,166) | 381,832 (U 259,100) | 15.6 (U 8.5) | + | nlohmann/json `json` | parse + dump | 3,417 | 5,153 | 13,425 | 504,511 | 18.5 | + | Glaze | generic (strict), minify_json [P] | 3,359 (P 417) | 6,437 (P 1,116) | 23,896 (P 2,113) | 480,814 (P 97,812) | 15.9 (P 1.9) | | serde_json (Rust) | Value | 4,012 | 6,583 | 17,306 | 509,702 | 16.5 | - | nlohmann/json `json` | parse + dump | 4,143 | 5,702 | 16,463 | 528,092 | 19.5 | **write** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 150 | 260 | 3,861 (N 998) | 63,958 | 0.8 | - | yyjson | write | 204 | 327 | 3,748 | 65,989 | 1.2 | - | sonic-cpp | Serialize | 256 | 541 | 5,120 | 69,546 | 1.6 | - | Glaze | generic, generic (strict) | 360 | 660 | 3,714 | 124,598 | 1.7 | - | nlohmann/json `json` | dump | 471 | 644 | 4,344 | 114,096 | 1.9 | - | simdjson | to_string | 359 | 637 | 4,999 | 154,364 | 1.9 | + | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 152 (N 147) | 260 | 3,811 (N 997) | 72,196 | 0.9 | + | yyjson | write | 180 | 325 | 3,548 | 71,044 | 1.2 | + | sonic-cpp | Serialize | 268 | 543 | 5,057 | 75,734 | 1.7 | + | Glaze | generic | 336 | 682 | 3,750 | 133,730 | 1.8 | + | nlohmann/json `json` | dump | 413 | 617 | 4,036 | 124,865 | 1.9 | + | simdjson | to_string | 360 | 636 | 5,363 | 169,032 | 1.9 | | sonic-rs (Rust) | Value | 434 | 927 | 3,987 | 109,176 | 2.8 | | simd-json (Rust) | borrowed | 582 | 800 | 5,147 | 122,623 | 2.8 | - | Boost.JSON | serialize | 480 | 1,016 | 6,154 | 139,022 | 2.5 | + | Boost.JSON | serialize | 474 | 1,003 | 6,012 | 148,120 | 2.5 | | serde_json (Rust) | Value | 550 | 887 | 4,005 | 134,163 | 3.3 | - | RapidJSON | Writer | 717 | 1,033 | 9,017 | 134,260 | 4.0 | + | RapidJSON | Writer | 661 | 1,015 | 8,980 | 136,957 | 4.1 | ??? info "Times in µs on x86-64 with Clang 23" @@ -2279,96 +2387,96 @@ cell for the times and the way. | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | accept | 255 | 512 | 1,055 | 34,729 | 1.1 | - | yyjson | insitu (copy untimed), read (raw numbers) | 334 | 811 | 2,580 | 83,153 | 1.5 | + | **nlohmann/json `json_document`** | accept | 250 | 509 | 1,032 | 34,454 | 1.1 | + | nlohmann/json `json` | accept | 335 | 711 | 2,018 | 39,513 | 1.4 | | sonic-rs (Rust) | IgnoredAny | 501 | 970 | 2,249 | 54,369 | 1.8 | - | Boost.JSON | SAX, SAX (numbers: none) | 571 | 910 | 1,684 | 55,498 | 2.3 | - | simdjson | DOM | 306 | 732 | 3,206 | 113,452 | 1.7 | - | sonic-cpp | Document [U] | – (U 448) | – (U 1,032) | – (U 3,143) | – (U 94,884) | – (U 1.8) | - | Glaze | validate_json | 588 | 1,431 | 1,867 | 75,571 | 2.4 | + | Boost.JSON | SAX, SAX (numbers: none) | 570 | 903 | 1,678 | 55,087 | 2.3 | + | simdjson | DOM | 302 | 721 | 3,202 | 116,859 | 1.7 | + | yyjson | insitu (copy), read, read (raw numbers) | 383 | 887 | 2,572 | 123,640 | 1.6 | + | sonic-cpp | Document [U] | – (U 451) | – (U 998) | – (U 3,031) | – (U 95,823) | – (U 1.8) | + | Glaze | validate_json | 594 | 1,230 | 2,139 | 73,235 | 2.5 | | serde_json (Rust) | IgnoredAny [U], from_utf8 + IgnoredAny | 624 (U 484) | 1,440 (U 1,370) | 2,406 (U 2,314) | 64,720 (U 58,247) | 2.4 (U 1.9) | - | nlohmann/json `json` | accept | 658 | 1,390 | 4,265 | 71,982 | 3.5 | | simd-json (Rust) | tape (copy) | 567 | 1,430 | 4,359 | 207,551 | 2.9 | - | RapidJSON | SAX (default precision), SAX (default precision) (no encoding check) [U], SAX (string stream, default precision), SAX (string stream, default precision) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,756 (U 1,132) | 1,971 (U 1,572) | 4,118 | 198,915 (U 131,204) | 8.2 (U 4.1) | + | RapidJSON | SAX (default precision), SAX (default precision) (no encoding check) [U], SAX (string stream, default precision), SAX (string stream, default precision) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,798 (U 1,173) | 1,944 (U 1,504) | 4,019 | 232,684 (U 128,516) | 8.1 (U 4.1) | **sum** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse | 374 | 923 | 3,846 | 85,286 | 1.8 | - | yyjson | insitu (copy untimed) | 399 | 946 | 3,445 | 88,447 | 1.9 | - | simdjson | DOM, On-Demand | 394 | 977 | 4,117 | 114,602 | 2.2 | + | **nlohmann/json `json_document`** | parse | 366 | 941 | 3,680 | 86,966 | 1.8 | + | nlohmann/json `json` | sax | 431 | 914 | 3,824 | 60,439 | 2.1 | + | simdjson | DOM, On-Demand | 391 | 969 | 4,121 | 116,855 | 2.2 | | sonic-rs (Rust) | visitor | 501 | 1,123 | 4,397 | 59,583 | 2.5 | - | sonic-cpp | Document [U] | – (U 502) | – (U 1,175) | – (U 3,754) | – (U 104,583) | – (U 2.1) | - | Boost.JSON | SAX | 580 | 928 | 5,809 | 56,774 | 2.4 | - | nlohmann/json `json` | sax | 728 | 1,393 | 5,428 | 89,481 | 3.9 | + | yyjson | insitu (copy), read | 436 | 1,048 | 3,454 | 133,577 | 1.9 | + | sonic-cpp | Document [U] | – (U 504) | – (U 1,176) | – (U 3,701) | – (U 104,509) | – (U 2.1) | + | Boost.JSON | SAX | 580 | 928 | 5,715 | 55,874 | 2.3 | | simd-json (Rust) | visitor (copy) | 681 | 1,650 | 5,083 | 210,185 | 3.5 | | serde_json (Rust) | visitor | 1,311 | 2,288 | 7,127 | 129,497 | 4.6 | - | RapidJSON | SAX, SAX (no encoding check) [U], SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,816 (U 1,187) | 2,224 (U 1,765) | 10,719 | 206,811 (U 135,945) | 8.5 (U 4.3) | - | Glaze | generic (double, strict), lazy_json [P] | 2,427 | 3,972 | 12,165 | 253,385 (P 196,582) | 9.2 (P 5.6) | + | RapidJSON | SAX, SAX (no encoding check) [U], SAX (string stream), SAX (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,846 (U 1,229) | 2,176 (U 1,761) | 10,561 | 239,499 (U 131,510) | 8.4 (U 4.3) | + | Glaze | generic (double) [P], generic (double, strict), lazy_json [P] | 2,374 | 3,982 | 11,930 | 261,204 (P 198,036) | 9.0 (P 5.6) | **select** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse, structs (from_json) | 320 | 759 | 1,390 | 88,083 | 1.6 | - | yyjson | insitu (copy untimed), read (raw numbers) | 351 | 788 | 2,632 | 92,551 | 1.6 | - | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P], On-Demand structs [P] | 316 (P 181) | 731 (P 495) | 3,262 (P 702) | 120,808 (P 48,823) | 1.9 (P 1.1) | + | **nlohmann/json `json_document`** | parse, structs (from_json) | 316 | 783 | 1,368 | 91,736 | 1.6 | + | yyjson | insitu (copy), read (raw numbers) | 386 | 891 | 2,572 | 135,813 | 1.7 | | sonic-rs (Rust) | structs | 504 | 1,033 | 2,328 | 90,486 | 2.0 | - | Glaze | structs (validate_skipped), structs [P] | 544 (P 345) | 1,368 (P 419) | 1,924 (P 444) | 85,745 (P 66,442) | 2.2 (P 1.6) | - | sonic-cpp | Document [U] | – (U 460) | – (U 1,011) | – (U 3,204) | – (U 108,245) | – (U 1.9) | + | simdjson | DOM, On-Demand (key selectors) [P], On-Demand [P] | 318 (P 182) | 728 (P 494) | 3,213 (P 692) | 129,986 (P 50,660) | 2.0 (P 1.1) | + | Glaze | structs (validate_skipped), structs [P] | 542 (P 337) | 1,369 (P 416) | 1,846 (P 447) | 83,907 (P 68,455) | 2.2 (P 1.6) | + | sonic-cpp | Document [U] | – (U 454) | – (U 1,015) | – (U 3,025) | – (U 112,621) | – (U 2.0) | | serde_json (Rust) | from_utf8 + structs, structs [U] | 701 | 1,532 | 2,529 (U 2,402) | 107,316 | 3.0 | - | Boost.JSON | parse, parse_into structs | 776 | 1,389 | 7,121 | 97,681 | 3.2 | + | Boost.JSON | parse, parse_into structs | 760 | 1,388 | 7,026 | 97,008 | 3.2 | | simd-json (Rust) | structs (copy) | 679 | 1,731 | 5,144 | 239,164 | 3.5 | - | RapidJSON | insitu (copy untimed), insitu (copy untimed) (no encoding check) [U] | 1,762 (U 1,144) | 2,198 (U 1,817) | 11,464 | 209,290 (U 142,852) | 8.2 (U 4.1) | - | nlohmann/json `json` | parse, structs (from_json) | 3,602 | 5,124 | 11,831 | 444,210 | 20.2 | + | RapidJSON | Document (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 1,806 (U 1,184) | 2,303 (U 1,929) | 11,395 | 257,762 (U 171,716) | 8.7 (U 4.3) | + | nlohmann/json `json` | parse, structs (from_json) | 3,181 | 4,583 | 9,599 | 405,734 | 18.9 | **edit** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 526 | 1,127 | 5,674 (N 2,443) | 196,370 | 2.9 | - | sonic-cpp | Document [U] | – (U 711) | – (U 1,472) | – (U 7,996) | – (U 183,852) | – (U 3.2) | - | yyjson | insitu (copy untimed) + mut copy, insitu (copy) + mut copy | 918 | 1,548 | 7,155 | 270,654 | 3.9 | + | **nlohmann/json `json_document`** | editable, editable (source numbers) [N] | 519 | 1,152 | 5,671 (N 2,502) | 195,716 | 3.0 | + | sonic-cpp | Document [U] | – (U 642) | – (U 1,497) | – (U 8,153) | – (U 184,321) | – (U 3.3) | + | yyjson | insitu (copy) + mut copy, mut copy | 833 | 1,674 | 7,249 | 316,652 | 4.2 | | sonic-rs (Rust) | Value | 1,197 | 2,389 | 7,744 | 390,952 | 6.9 | - | Boost.JSON | parse | 1,855 | 2,596 | 12,765 | 350,286 | 8.3 | + | Boost.JSON | parse | 1,716 | 2,578 | 13,427 | 354,510 | 8.9 | | simd-json (Rust) | borrowed (copy), owned (copy) | 1,862 | 4,194 | 14,896 | 391,369 | 9.0 | - | RapidJSON | insitu (copy untimed), insitu (copy untimed) (no encoding check) [U], insitu (copy) | 2,635 (U 1,952) | 3,103 (U 2,728) | 21,139 | 333,913 (U 267,081) | 11.8 (U 7.7) | - | nlohmann/json `json` | ordered_json, parse | 3,069 | 4,905 | 16,702 | 666,261 | 13.1 | + | RapidJSON | Document (string stream) (no encoding check) [U], insitu (copy), insitu (copy) (no encoding check) [U] | 2,478 (U 1,842) | 3,261 (U 2,887) | 21,686 | 378,727 (U 294,727) | 13.1 (U 8.3) | + | nlohmann/json `json` | ordered_json, parse | 2,452 | 4,402 | 14,765 | 646,489 | 12.6 | | serde_json (Rust) | Value | 4,042 | 6,586 | 16,631 | 632,766 | 17.6 | - | Glaze | generic (strict), generic [P] | 4,341 | 7,322 | 26,786 | 725,881 | 18.3 (P 17.7) | + | Glaze | generic (strict), generic [P] | 3,896 | 7,671 | 28,104 (P 27,111) | 723,675 | 20.2 | **minify** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N] | 518 | 1,040 | 5,043 (N 2,456) | 146,125 | 2.4 | - | yyjson | insitu (copy untimed) + write, read (raw numbers) + write [N] | 620 | 1,189 | 6,283 (N 3,472) | 154,016 | 3.0 | - | sonic-cpp | Document + Serialize [U] | – (U 710) | – (U 1,485) | – (U 8,122) | – (U 166,935) | – (U 3.1) | - | simdjson | DOM + to_string, minify [P] | 783 (P 105) | 1,395 (P 269) | 8,303 (P 337) | 275,007 (P 45,087) | 4.0 (P 0.5) | + | **nlohmann/json `json_document`** | parse + dump, parse + dump (source numbers) [N] | 472 | 1,055 | 5,041 (N 2,363) | 149,143 | 2.5 | + | yyjson | read (raw numbers) + write [N], read + write | 626 | 1,261 | 6,179 (N 3,426) | 200,886 | 3.3 | + | sonic-cpp | Document + Serialize [U] | – (U 655) | – (U 1,474) | – (U 7,856) | – (U 169,992) | – (U 3.5) | + | simdjson | DOM + to_string, minify [P] | 706 (P 99.0) | 1,411 (P 270) | 8,125 (P 329) | 270,332 (P 42,546) | 4.3 (P 0.5) | | sonic-rs (Rust) | Value | 1,000 | 2,097 | 7,795 | 215,280 | 5.4 | - | Boost.JSON | parse + serialize | 1,924 | 2,569 | 12,622 | 329,090 | 8.4 | + | Boost.JSON | parse + serialize | 1,722 | 2,573 | 12,678 | 330,878 | 9.0 | | simd-json (Rust) | borrowed (copy) | 1,889 | 4,196 | 15,716 | 414,377 | 9.2 | - | RapidJSON | Document + Writer, SAX to Writer, SAX to Writer (no encoding check) [U], SAX to Writer (raw numbers) (no encoding check) [U N], SAX to Writer (raw numbers) [N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 2,808 (U N 2,022) | 2,975 (U 2,480) | 22,589 (N 14,986) | 358,807 (U 261,527) | 12.7 (U 7.2) | + | RapidJSON | Document + Writer, SAX to Writer, SAX to Writer (no encoding check) [U], SAX to Writer (raw numbers) (no encoding check) [U N], SAX to Writer (string stream), SAX to Writer (string stream) (no encoding check) [U] | 2,456 (U 1,794) | 2,864 (U 2,444) | 21,753 (U N 14,707) | 338,985 (U 252,656) | 13.6 (U 7.5) | + | nlohmann/json `json` | parse + dump | 3,525 | 5,178 | 14,024 | 506,705 | 20.6 | | serde_json (Rust) | Value | 4,012 | 6,583 | 17,306 | 509,702 | 16.5 | - | nlohmann/json `json` | parse + dump | 4,445 | 5,713 | 16,711 | 543,033 | 21.4 | - | Glaze | generic (strict), minify_json [P] | 4,132 (P 444) | 7,465 (P 1,059) | 26,962 (P 2,222) | 528,811 (P 91,153) | 17.2 (P 1.9) | + | Glaze | generic (strict), minify_json [P] | 3,801 (P 422) | 7,482 (P 1,051) | 26,932 (P 2,140) | 544,342 (P 87,842) | 19.2 (P 2.1) | **write** | library | variant | `twitter.json` | `citm_catalog.json` | `canada.json` | `jeopardy.json` | one status (2.5 KB) | | --- | --- | --: | --: | --: | --: | --: | - | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 160 (N 154) | 311 (N 279) | 3,714 (N 989) | 67,038 (N 64,301) | 0.9 | - | yyjson | write | 218 | 386 | 3,476 | 71,247 | 1.4 | - | sonic-cpp | Serialize | 224 | 475 | 4,850 | 65,131 | 1.3 | - | nlohmann/json `json` | dump | 451 | 628 | 4,506 | 116,529 | 2.0 | - | Glaze | generic | 399 | 748 | 4,220 | 128,020 | 2.0 | - | simdjson | to_string | 389 | 656 | 4,896 | 157,616 | 2.1 | + | **nlohmann/json `json_document`** | dump, dump (source numbers) [N] | 158 (N 153) | 279 | 3,637 (N 982) | 67,366 | 0.9 | + | yyjson | write | 216 | 367 | 3,366 | 73,272 | 1.4 | + | sonic-cpp | Serialize | 228 | 474 | 4,766 | 66,619 | 1.3 | + | nlohmann/json `json` | dump | 438 | 599 | 4,458 | 116,318 | 1.9 | + | Glaze | generic, generic (strict) | 396 | 725 | 4,107 | 132,573 | 2.1 | + | simdjson | to_string | 382 | 661 | 4,787 | 162,220 | 2.1 | | sonic-rs (Rust) | Value | 434 | 927 | 3,987 | 109,176 | 2.8 | - | Boost.JSON | serialize | 478 | 916 | 5,615 | 141,846 | 2.5 | + | Boost.JSON | serialize | 478 | 913 | 5,610 | 141,994 | 2.6 | | simd-json (Rust) | borrowed | 582 | 800 | 5,147 | 122,623 | 2.8 | | serde_json (Rust) | Value | 550 | 887 | 4,005 | 134,163 | 3.3 | - | RapidJSON | Writer | 627 | 1,008 | 9,596 | 123,764 | 3.7 | + | RapidJSON | Writer | 633 | 960 | 9,388 | 127,319 | 3.8 | ### F. Behavior changes @@ -2398,8 +2506,8 @@ because 3.12.0 has no BON8. | machine | Apple M1 Max laptop on AC power, macOS 27.0.1 | | compiler | Apple clang 21, libc++ | | flags | `-O3 -flto -DNDEBUG`, the compiler's default standard, C++14 | -| suite | 3 rounds × 4 repetitions per version, random interleaving, each round of a version started only when the load average was below 4 and repeated when it rose above 5 while running; of 3.12.0 and 3.13.0, the round that was 4.2% and 1.8% slower overall than the other two was dropped; the median of the remaining 8 | -| noise | the coefficient of variation was 3% or less for 172 of the 182 rows with 3.12.0 and 174 with 3.13.0; the largest was 7.3%, not counting one 3.12.0 run of a 1.5 µs benchmark that a disturbance made 1.4× as slow | +| suite | 3 rounds × 4 repetitions per version, random interleaving, each round of a version started only when the load average was below 4 and repeated when it rose above 5 while running; the rounds of a version differed by less than 1% overall; the median of all 12 | +| noise | the coefficient of variation was 3% or less for 180 of the 182 rows with 3.12.0 and 179 with 3.13.0; the largest was 5.4% (`Iterate/twitter` with 3.13.0), not counting two disturbed runs: two repetitions of a 0.4 µs benchmark with 3.12.0 that took 2.4× as long, and one of a 46 ms one with 3.13.0 that took 1.4× as long | | inputs | the files of [Appendix A](#a-test-files), and number-heavy files | | Intel check | the suite again on a MacBook Pro (15-inch, 2016) with an Intel Core i7-6920HQ (Skylake, 4 cores), macOS 12.0.1, Apple clang 13, the same flags with C++11; started below a load of 3, repeated above 4; the first round of each version dropped (3.12.0's was 4.7% slower than the others); the coefficient of variation was 3% or less for 120 rows with 3.12.0 and 161 of 234 with 3.13.0, so single rows are less reliable than on the M1 Max; geometric mean 3.07×, and two rows below 1×: copying `citm_catalog.json` without destroying the copy (0.93×) and `==` of it and a copy (0.95×), both in each round | @@ -2407,34 +2515,35 @@ because 3.12.0 has no BON8. **The other measurements.** The timeline is [`tests/benchmarks/history`](https://github.com/nlohmann/json/tree/develop/tests/benchmarks/history): one program, -compiled against `single_include` of 3.12.0, of each of the 351 commits on `develop` up to November 3 that changed +compiled against `single_include` of 3.12.0, of each of the 358 commits on `develop` up to November 7 that changed `json.hpp` or `json_view.hpp` since, and of 3.13.0 (C++17, `-O3`, no LTO), 4 rounds in random order, median of the rounds (the mean of the two middle ones). Two commits that don't compile on their own are left out. The commits up to -October 27 were run on October 27, the later ones and 3.13.0 on November 3, together with twelve of the earlier commits -as a check: per workload, their median moved by 1.5% or less. The algorithmic fixes come from small extra programs -(C++17, `-O3`, median of 3 interleaved runs). The comparison with other libraries is described in [Appendix -E](#e-comparison-with-other-libraries). +October 27 were run on October 27, those up to November 2 on November 3, and the later ones and 3.13.0 on November 7, +each time together with twelve of the earlier commits as a check: per workload, their median moved by 1.5% or less +(3% for `json_view`'s parse of `canada.json`, which only three of them have). The algorithmic fixes come from small +extra programs (C++17, `-O3`, median of 3 interleaved runs). The comparison with other libraries is described in +[Appendix E](#e-comparison-with-other-libraries). **Compile times.** The two programs of [The price](#the-price), each compiled as one translation unit with `-std=c++20` (modules need it) and `-O0` or `-O2`, with Apple clang 21 and Homebrew GCC 16.2 on the M1 Max. The time is the CPU time (user plus system) of the compiler process, the median of 5 rounds in which all 40 compilations ran in a random order. -The slowest of the 5 runs was within 13% of the fastest for every program and every build of the precompiled header or +The slowest of the 5 runs was within 14% of the fastest for every program and every build of the precompiled header or the module. Every variant was also linked and run once, with the same output from all of them. | build | program | 3.12.0 | 3.13.0 | 3.13.0, PCH | 3.13.0, module | | --- | --- | --: | --: | --: | --: | -| Apple clang `-O0` | `count_numbers` | 955 | 1,320 | 689 | 918 | -| | `server_config` | 1,113 | 1,496 | 863 | 1,136 | -| Apple clang `-O2` | `count_numbers` | 1,393 | 2,081 | 1,458 | 1,685 | -| | `server_config` | 1,754 | 2,546 | 1,925 | 2,169 | -| GCC 16 `-O0` | `count_numbers` | 1,569 | 2,548 | 1,725 | 1,700 | -| | `server_config` | 2,025 | 2,985 | 2,302 | 2,326 | -| GCC 16 `-O2` | `count_numbers` | 2,336 | 3,702 | 2,987 | 2,913 | -| | `server_config` | 3,122 | 4,714 | 4,006 | 4,055 | +| Apple clang `-O0` | `count_numbers` | 831 | 1,131 | 639 | 790 | +| | `server_config` | 963 | 1,313 | 774 | 1,037 | +| Apple clang `-O2` | `count_numbers` | 1,328 | 1,898 | 1,312 | 1,491 | +| | `server_config` | 1,584 | 2,251 | 1,779 | 1,926 | +| GCC 16 `-O0` | `count_numbers` | 1,417 | 2,385 | 1,593 | 1,604 | +| | `server_config` | 1,878 | 2,820 | 2,152 | 2,180 | +| GCC 16 `-O2` | `count_numbers` | 2,159 | 3,465 | 2,765 | 2,740 | +| | `server_config` | 2,878 | 4,388 | 3,722 | 3,774 |Compile time in milliseconds of the two programs in [The price](#the-price) (CPU -time of the compiler, median of 5 runs). Building the precompiled header took 973 and 987 ms with Apple clang (`-O0`, -`-O2`) and 2,565 and 2,589 ms with GCC; building the module took 1,617 and 1,602 ms or 2,499 and 2,465 ms.
+time of the compiler, median of 5 runs). Building the precompiled header took 917 and 879 ms with Apple clang (`-O0`, +`-O2`) and 2,306 and 2,264 ms with GCC; building the module took 1,399 and 1,433 ms or 2,339 and 2,325 ms. ??? example "The programs and the build commands" @@ -2566,19 +2675,21 @@ suite also on an Intel laptop with Clang. The comparison with other libraries al which Haswell doesn't have, so the real CPU is probably a later one. It shares its cores with other work, so its differences of a few percent are noise. The theme sections quote x86-64 numbers where a pull request measured them, mostly with GCC 13 and Clang 18. Expect smaller gains for some workloads on x86-64 with glibc: its `FILE*` locking is -cheaper to begin with, and its allocator behaves differently. We did not measure on Windows, and we did not measure -binary size or memory use. +cheaper to begin with, so taking it once per parse gains less, its allocator behaves differently, and fresh output +memory costs more page faults. We did not measure on Windows, and we did not measure binary size or memory use. ### H. Pull requests by theme - 1 **Specialize for each input source:** contiguous input - [#5283][5283]; streams and `FILE*` [#5828][5828], [#5884][5884]; binary strings in bulk [#5233][5233]; sized sentinels - [#5268][5268]. + [#5283][5283]; streams and `FILE*` [#5828][5828], [#5884][5884]; the buffer of a `FILE` [#6030][6030]; iterator + pairs [#6029][6029]; one loop for parser and lexer [#6031][6031]; binary strings in bulk [#5233][5233]; sized + sentinels [#5268][5268]. - 2 **Work on words, not bytes:** word-at-a-time strings, UTF-8 runs, and digits [#5283][5283], [#5738][5738], [#5851][5851]; indentation [#5810][5810]; `dump()` runs [#5285][5285]; SSE2 and NEON [#5853][5853], [#5628][5628]; `json_document` with `ensure_ascii` [#5879][5879]; short strings [#5878][5878], [#5883][5883]; escape table [#5822][5822]; loop shape [#5490][5490]; fraction digits [#5971][5971]; - BSON keys [#5968][5968]; UTF-8 checks of binary strings [#5982][5982]; one SIMD macro set [#5921][5921]. + BSON keys [#5968][5968]; UTF-8 checks of binary strings [#5982][5982]; one SIMD macro set [#5921][5921]; fewer + operations per word and vector [#6033][6033]. - 3 **Own the number conversions:** Eisel-Lemire [#5617][5617]; long numbers [#5738][5738]; one pass over the digits [#5851][5851]; Żmij [#5634][5634]; long integers in `dump()` [#5964][5964]; MSVC intrinsics for the 128-bit multiplication [#5782][5782]. @@ -2587,13 +2698,15 @@ binary size or memory use. [#5501][5501]; `ordered_json` growth [#5609][5609]; arrays at their final size [#5882][5882]; moving keys [#5458][5458], [#5855][5855]; separator check [#5592][5592]; CBOR keys [#5328][5328]; `accept()` without copies [#5978][5978]; runs of scalars in binary arrays [#5975][5975], [#5980][5980], [#5981][5981], [#6008][6008]; BJData - marker list [#5555][5555]; `from_json()` into containers [#5833][5833]. + marker list [#5555][5555]; `from_json()` into containers [#5833][5833]; binary strings without a zero fill + [#6028][6028]. - 5 **Read the assembly:** stack canary [#5854][5854]; exceptions and inlining [#5838][5838], [#5840][5840], [#5839][5839], [#5836][5836]; aliasing [#5633][5633]; forced - inlining [#5878][5878]; branch-free selections [#5908][5908]; code size [#5902][5902]. + inlining [#5878][5878]; branch-free selections [#5908][5908]; code size [#5902][5902]; the order of the iterator's + type tests [#6039][6039]. - 6 **Write through a cursor:** write buffer [#5285][5285]; adapter on the stack [#5449][5449]; binary sinks [#5286][5286]; cursor and runs of numbers [#5824][5824], - [#5885][5885]; `to_bson()` in one pass [#5979][5979]. + [#5885][5885]; `to_bson()` in one pass [#5979][5979]; a window instead of zeroing the whole capacity [#6020][6020]. - 7 **Fix the algorithm first:** `diff()` [#5461][5461]; parse callbacks [#5457][5457]; `operator<` [#5390][5390]; `flatten()` [#5834][5834]; `unflatten()` [#5793][5793]; JSON Pointer and Patch [#5861][5861], [#5886][5886]; `to_bson()` [#5553][5553]; `JSON_DIAGNOSTICS` [#5832][5832]; bounded @@ -2681,7 +2794,15 @@ These are the checks behind the themes: [Appendix F](#f-behavior-changes). - **Stream windows** (theme 1). A test `streambuf` hands out get areas of 1–4,096 bytes, so that every token crosses a window edge at every offset, and the result is compared with a - `streambuf` without a get area, which takes the old path. + `streambuf` without a get area, which takes the old path. The `FILE` window is checked the same way, with `FILE` + buffers of 1 to 70 bytes, unbuffered files, and characters pushed back with `ungetc()` before the parse, on macOS and + glibc. +- **The fused parser** (theme 1). `contiguous_parser` is + compared with the byte path on corner cases of literals, numbers, escapes, surrogates and UTF-8, on 1,500 generated + documents, every truncation of the short ones and random mutations, and on nesting across the 64-level words of its + stack: the same values with the same number types and float bits, the same exception messages, the same `accept()` + verdicts and the same `sax_parse()` events, also for handlers that stop early (242,118 assertions). A fuzzer that + compares the `sax_parse()` events of both paths ran 9 million inputs without a finding. - **Differential fuzzing of the input kinds.** Every fuzz input is also parsed from an `std::istringstream`, from `std::list` iterators (the byte path) and from a `FILE*`, and the value, the exception id, the message, and the byte offset must be identical ([#5827][5827]). @@ -2792,3 +2913,10 @@ These are the checks behind the themes: [5981]: https://github.com/nlohmann/json/pull/5981 [5982]: https://github.com/nlohmann/json/pull/5982 [6008]: https://github.com/nlohmann/json/pull/6008 +[6020]: https://github.com/nlohmann/json/pull/6020 +[6028]: https://github.com/nlohmann/json/pull/6028 +[6029]: https://github.com/nlohmann/json/pull/6029 +[6030]: https://github.com/nlohmann/json/pull/6030 +[6031]: https://github.com/nlohmann/json/pull/6031 +[6033]: https://github.com/nlohmann/json/pull/6033 +[6039]: https://github.com/nlohmann/json/pull/6039 diff --git a/docs/mkdocs/docs/images/perf_blog_distribution.svg b/docs/mkdocs/docs/images/perf_blog_distribution.svg index 4760bfd85..313897a5c 100644 --- a/docs/mkdocs/docs/images/perf_blog_distribution.svg +++ b/docs/mkdocs/docs/images/perf_blog_distribution.svg @@ -1,34 +1,34 @@