Write the members of an object with scalar values without a call each

Every member of an object was a call of write_cbor()/write_msgpack()/
write_ubjson() for its value, however simple: a function with a large frame
that saves and restores many registers around a single sink write.

Objects are now written by a function of their own, like arrays, in which
members with a null, boolean, integer, or string value (and a float, in UBJSON
and BJData) are written in place; anything else is passed on to the general
function. The depth limit and the recursion for nested values are unchanged.

Instructions per call (Linux, GCC 13, thousands), to_ubjson: 20000 small objects 17861 -> 14661, twitter 3271 -> 2798; to_bjdata: 20000 small objects 17881 -> 14681, twitter 3277 -> 2804; to_msgpack: twitter 2948 -> 2646.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:29:06 +02:00
1 parent eb251df17c
commit f9e2302af9
2 files changed
+326 -52

No files matched your search

+163 -26
View File
@@ -305,16 +305,7 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
write_cbor_head(0xA0, j.m_data.m_value.object->size());
for (const auto& el : *j.m_data.m_value.object)
{
// el.first is written directly (not via a temporary
// basic_json), with the object as diagnostics context
write_cbor_key(el.first, j);
write_cbor(el.second, depth + 1);
}
write_cbor_object(j, depth);
break;
}
@@ -530,15 +521,7 @@ class binary_writer
case value_t::object:
{
// step 1: write control byte and the object size
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
for (const auto& el : *j.m_data.m_value.object)
{
// as in write_cbor, el.first is written directly with the
// object as diagnostics context
write_msgpack_key(el.first, j);
write_msgpack(el.second, depth + 1);
}
write_msgpack_object(j, depth);
break;
}
@@ -708,13 +691,7 @@ class binary_writer
bool prefix_required = true;
const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
for (const auto& el : *j.m_data.m_value.object)
{
// a converted key must outlive the reference returned by sanitize_utf8_for_write
const object_key_string_t key_string = el.first;
write_ubjson_text(key_string, j, false, use_bjdata);
write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1);
}
write_ubjson_members(j, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth);
if (write_closer)
{
@@ -2579,6 +2556,56 @@ class binary_writer
return encode_first_byte_and_tail(out, first_byte, size_class, argument);
}
/*!
@brief write an object value (head and members) as CBOR
A member whose value is null, a boolean, an integer, or a string is written
without a call of @ref write_cbor. Kept out of line, like @ref write_cbor_array.
*/
JSON_HEDLEY_NEVER_INLINE
void write_cbor_object(const BasicJsonType& j, const std::size_t depth)
{
// step 1: write control byte and the object size
write_cbor_head(0xA0, j.m_data.m_value.object->size());
for (const auto& el : *j.m_data.m_value.object)
{
// el.first is written directly (not via a temporary
// basic_json), with the object as diagnostics context
write_cbor_key(el.first, j);
const BasicJsonType& value = el.second;
switch (value.type())
{
case value_t::null:
oa.write_character(to_char_type(0xF6));
break;
case value_t::boolean:
oa.write_character(value.m_data.m_value.boolean ? to_char_type(0xF5) : to_char_type(0xF4));
break;
case value_t::number_integer:
case value_t::number_unsigned:
write_cbor_integer(value, std::integral_constant<bool, integers_fit_64_bits()>());
break;
case value_t::string:
write_cbor_string(*value.m_data.m_value.string, value);
break;
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::number_float:
case value_t::discarded:
default:
write_cbor(value, depth + 1);
break;
}
}
}
/*!
@brief write an array value (head and elements) as CBOR
@@ -2857,6 +2884,54 @@ class binary_writer
}
}
/*!
@brief write an object value (head and members) as MessagePack
See @ref write_cbor_object.
*/
JSON_HEDLEY_NEVER_INLINE
void write_msgpack_object(const BasicJsonType& j, const std::size_t depth)
{
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
for (const auto& el : *j.m_data.m_value.object)
{
// as in write_cbor, el.first is written directly with the
// object as diagnostics context
write_msgpack_key(el.first, j);
const BasicJsonType& value = el.second;
switch (value.type())
{
case value_t::null:
oa.write_character(to_char_type(0xC0));
break;
case value_t::boolean:
oa.write_character(value.m_data.m_value.boolean ? to_char_type(0xC3) : to_char_type(0xC2));
break;
case value_t::number_integer:
case value_t::number_unsigned:
write_msgpack_integer(value, std::integral_constant<bool, integers_fit_64_bits()>());
break;
case value_t::string:
write_msgpack_string(*value.m_data.m_value.string, value);
break;
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::number_float:
case value_t::discarded:
default:
write_msgpack(value, depth + 1);
break;
}
}
}
/*!
@brief write an array value (head and elements) as MessagePack
@@ -3248,6 +3323,68 @@ class binary_writer
}
}
/*!
@brief write the members of the object @a j of a UBJSON/BJData container
A member whose value is null, a boolean, an integer, a float, or a string is
written without a call of @ref write_ubjson. Kept out of line, like
@ref write_cbor_object.
*/
JSON_HEDLEY_NEVER_INLINE
void write_ubjson_members(const BasicJsonType& j, const bool use_count, const bool use_type,
const bool prefix_required, const bool use_bjdata, const bjdata_version_t bjdata_version,
const std::size_t depth)
{
for (const auto& el : *j.m_data.m_value.object)
{
// a converted key must outlive the reference returned by sanitize_utf8_for_write
const object_key_string_t key_string = el.first;
write_ubjson_text(key_string, j, false, use_bjdata);
const BasicJsonType& value = el.second;
switch (value.type())
{
case value_t::null:
if (prefix_required)
{
oa.write_character(to_char_type('Z'));
}
break;
case value_t::boolean:
if (prefix_required)
{
oa.write_character(value.m_data.m_value.boolean ? to_char_type('T') : to_char_type('F'));
}
break;
case value_t::number_integer:
write_number_with_ubjson_prefix(value.m_data.m_value.number_integer, prefix_required, use_bjdata);
break;
case value_t::number_unsigned:
write_number_with_ubjson_prefix(value.m_data.m_value.number_unsigned, prefix_required, use_bjdata);
break;
case value_t::number_float:
write_number_with_ubjson_prefix(value.m_data.m_value.number_float, prefix_required, use_bjdata);
break;
case value_t::string:
write_ubjson_text(*value.m_data.m_value.string, value, prefix_required, use_bjdata);
break;
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
write_ubjson(value, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1);
break;
}
}
}
/*!
@brief write a UBJSON/BJData string or object key: the optional string
marker `S`, the length as an integer with its marker, and the characters