From 9593feabf36524502b413b3583c9814eee5d4550 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 7 Nov 2026 14:54:11 +0100 Subject: [PATCH] docs: blog post: name every user of the depth counter and every input the document copies The "Bounded recursion" paragraph said only copying and equality for custom containers use the thread_local counter. Converting from another basic_json specialization and the ordering comparisons use it too; equality for the standard containers passes the depth instead. The "Strings stay in the input" bullet named only parse_copy() and an rvalue std::string as owning inputs. Streams, FILE* and other input that is not one contiguous block of bytes are read into an owned buffer as well. Signed-off-by: Niels Lohmann --- .../docs/home/blog/how_3_13_0_got_faster.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/mkdocs/docs/home/blog/how_3_13_0_got_faster.md b/docs/mkdocs/docs/home/blog/how_3_13_0_got_faster.md index dd2456e7b..7f6417e38 100644 --- a/docs/mkdocs/docs/home/blog/how_3_13_0_got_faster.md +++ b/docs/mkdocs/docs/home/blog/how_3_13_0_got_faster.md @@ -1108,12 +1108,13 @@ static void destroy_bounded(basic_json& v, std::size_t depth) noexcept

Bounded recursion: recursive down to 128 levels, iterative below.

The first 128 levels run the old recursive code, and only below that does an explicit stack, or the walk below, take -over. A document nested less deeply pays one comparison per container. Comparing walks `std::vector` and `std::map` -itself and passes the depth as an argument. Copying can't do that cheaply: the containers' copy constructors call ours -for every element, and a copy that inserts element by element so that it can pass the depth measured 4–16% slower, -because `std::map`'s own copy clones its tree without comparing a single key. So copying counts the levels in a -`thread_local` counter instead, about ten nanoseconds per copy of a small object, and so does comparing for custom -container types. +over. A document nested less deeply pays one comparison per container. Testing for equality walks `std::vector` and +`std::map` itself and passes the depth as an argument. Copying can't do that cheaply: the containers' copy constructors +call ours for every element, and a copy that inserts element by element so that it can pass the depth measured 4–16% +slower, because `std::map`'s own copy clones its tree without comparing a single key. So copying counts the levels in a +`thread_local` counter instead, about ten nanoseconds per copy of a small object. Three more operations use the same +counter: converting a value from another `basic_json` specialization, the ordering comparisons (`<`, `<=>` and the +operators derived from them), and equality for custom container types, whose own `operator==` calls ours. ### Destroying without a stack @@ -1186,7 +1187,9 @@ static_assert(sizeof(node) == 16, "node must stay 16 bytes"); - **Strings stay in the input.** Only strings with escapes are decoded, into an arena. A document makes only a handful of allocations, no matter how many values it has. The price is that the text must outlive the document, unless the document owns it: [`parse_copy()`](../../api/basic_json_document/parse_copy.md) copies the text, and parsing an - rvalue `std::string` moves it into the document. Offsets are 32 bits, which limits a document to 4 GiB. + rvalue `std::string` moves it into the document. Input that can't be read as one contiguous block of bytes, such as a + stream or a `FILE*`, is read into a buffer that the document owns, too. Offsets are 32 bits, which limits a document + to 4 GiB. - **Integers are converted while their digits are in the cache,** into the last 8 bytes of their node. - **Floats stay text until you read them.** The node records the number of digits before and after the point, and reading converts with the same Clinger/Eisel-Lemire code as `json::parse`. We tried converting them during the parse.