CBOR/MessagePack readers: leave one-element arrays to the general path (#6008)

enter_cbor_array and enter_msgpack_array called the out-of-line scalar
run for every non-empty array. When the array's element is an object or
an array, as on every level of [[...]] or the single ring of a GeoJSON
polygon, the call reads nothing; it made 1,000 arrays nested in each
other cost 4 % more instructions (GCC 13) and up to 8 % more time (Apple
clang) than without the run. #5975 had fixed that with an inline
pre-check, which was later dropped after measuring canada only.

The run is now entered only for arrays of two or more elements: the same
comparison as before, so no other row changes. Arrays of one scalar,
which the run read faster, now take the general path; they are rare in
documents (one in citm_catalog, none in twitter or canada). The new
benchmark FromBinaryShape/singles measures them, next to
FromBinaryShape/nested.

Instructions per repetition, new/old, GCC 13 callgrind: nested arrays
0.973 (CBOR) and 0.971 (MessagePack), arrays of one scalar 1.037 and
1.021, every other row within 0.15 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann authored and GitHub committed 2026-11-02 18:25:44 +01:00
1 parent 590b622d48
commit 6d5581c4e8
5 files changed
+96 -6

No files matched your search

+28
View File
@@ -2544,6 +2544,34 @@ TEST_CASE("CBOR arrays of scalars from contiguous and non-contiguous input")
}
}
TEST_CASE("CBOR arrays of one element from contiguous and non-contiguous input")
{
// contiguous input leaves an array of one element to the general path
// rather than to the loop that reads arrays of scalars; both inputs must
// still give the same events, value, or error for the complete document
// and for every truncation of it, with enough bytes after each array that
// the loop could have been entered
const json j = json::parse(R"({"a": [[[[1.5]]]], "b": [7], "c": [{"x": 2.5}], "d": ["s"], "e": [[1, 2]], "f": [null], "g": 1.25})");
const std::vector<std::uint8_t> bytes = json::to_cbor(j);
CHECK(json::from_cbor(bytes) == j);
for (std::size_t length = 0; length <= bytes.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_cbor(contiguous, true, false).dump() == json::from_cbor(bytewise.begin(), bytewise.end(), true, false).dump());
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::cbor, true, false)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false));
CHECK(from_contiguous.events == from_bytewise.events);
}
}
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
{
// Reading an indefinite-length string or byte array used to call itself
+28
View File
@@ -2311,3 +2311,31 @@ TEST_CASE("MessagePack arrays of scalars from contiguous and non-contiguous inpu
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack));
}
}
TEST_CASE("MessagePack arrays of one element from contiguous and non-contiguous input")
{
// contiguous input leaves an array of one element to the general path
// rather than to the loop that reads arrays of scalars; both inputs must
// still give the same events, value, or error for the complete document
// and for every truncation of it, with enough bytes after each array that
// the loop could have been entered
const json j = json::parse(R"({"a": [[[[1.5]]]], "b": [7], "c": [{"x": 2.5}], "d": ["s"], "e": [[1, 2]], "f": [null], "g": 1.25})");
const std::vector<std::uint8_t> bytes = json::to_msgpack(j);
CHECK(json::from_msgpack(bytes) == j);
for (std::size_t length = 0; length <= bytes.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_msgpack(contiguous, true, false).dump() == json::from_msgpack(bytewise.begin(), bytewise.end(), true, false).dump());
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::msgpack, true, false)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack, true, false));
CHECK(from_contiguous.events == from_bytewise.events);
}
}