Build arrays of the DOM parser in one exact-size allocation

Reading an array of unknown length appended its elements to a std::vector
one at a time, so a small array reallocated (and moved its elements) at
sizes 1, 2, 4, ... The DOM SAX parser now collects the elements of arrays
whose length is unknown on one stack shared by all open arrays and moves
them into an array of exactly the right size when the closing bracket
arrives. An array that grows past 16 elements switches to appending to its
own vector, so large arrays pay no second copy. Binary formats, which
announce the length, and builds with JSON_DIAGNOSTICS or
JSON_DIAGNOSTIC_POSITIONS (whose values need their final place from the
start) keep storing into the array directly.

The stacks of open containers are reserved on first use instead of growing
from one element.

The binary readers get a DOM parser of their own, json_sax_dom_binary_parser:
it is the plain parser that stores every value directly in its container
(the code the readers used before). Their containers announce their size, so
the bookkeeping of the collecting parser (frame entries, the destination of a
finished container) was pure overhead for them: with the collecting parser
FromBinaryShape nested was 3-12 % and containers up to 5 % more instructions
(GCC) than before. With the plain parser Linux Ir of FromBinaryShape and
FromBinaryBuffer is within 0.8 % of develop (Clang: identical).

canada.json has 56 000 two-element arrays that took three allocations each
and now take two. DOM parse Ir on Linux against develop (GCC / Clang): canada
-7.0 % / -7.7 %, citm -0.8 % / -1.6 %, 124-byte object -5.4 % / -5.5 %;
accept and SAX are untouched.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:27:51 +02:00
1 parent 56ea5dd6bf
commit 2e454713d0
4 files changed
+816 -10

No files matched your search

+4 -2
View File
@@ -161,6 +161,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template<typename BasicJsonType, typename InputAdapterType>
friend class ::nlohmann::detail::json_sax_dom_parser;
template<typename BasicJsonType, typename InputAdapterType>
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
template<typename BasicJsonType, typename InputAdapterType>
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
#if JSON_DIAGNOSTIC_POSITIONS
friend struct ::nlohmann::detail::diagnostic_positions;
@@ -6287,7 +6289,7 @@ public:
from_ubjson/from_bjdata/from_bon8/from_bson overloads
Building the @ref binary_reader as a named local, rather than as a
temporary that @ref detail::json_sax_dom_parser::parse is called on in
temporary that @ref detail::json_sax_dom_binary_parser::parse is called on in
the same expression, avoids a false-positive cppcheck accessMoved
warning in each of the 16 callers.
*/
@@ -6298,7 +6300,7 @@ public:
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{
basic_json result;
detail::json_sax_dom_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
detail::json_sax_dom_binary_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
if (!reader.sax_parse(&sdp, strict, tag_handler))
{