Write a UBJSON, BJData or BSON string and its head in one reservation

Same as for CBOR and MessagePack: with the default error handler (keep) a
string or object key of up to 253 characters (UBJSON, BJData: the `S` marker,
the length as `i` or `U` and one byte, and the characters), or a BSON string
entry that fits the reservation limit (type, name, length, characters and null
byte), is written into space reserved from the sink in one go, instead of by
four to six sink writes, a string_t for the error handler, and a call of
sanitize_utf8_for_write(). The name of a BSON entry is copied with the short-
string copy of the sinks.

Instructions per call (Linux, GCC 13, thousands), to_ubjson: 20000 small objects 28241 -> 20981, twitter 5175 -> 3867, citm_catalog 10681 -> 8857; to_bjdata: 20000 small objects 28261 -> 21001, twitter 5181 -> 3873.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:28:59 +02:00
1 parent ecc2f511c7
commit a517b8ea8b
2 files changed
+132 -50

No files matched your search

+66 -25
View File
@@ -25449,17 +25449,7 @@ class binary_writer
case value_t::string:
{
string_t storage;
const string_t& value = sanitize_utf8_for_write(*j.m_data.m_value.string, j, storage);
if (add_prefix)
{
oa.write_character(to_char_type('S'));
}
write_number_with_ubjson_prefix(value.size(), true, use_bjdata);
oa.write_characters(
reinterpret_cast<const CharType*>(value.data()),
value.size());
write_ubjson_text(*j.m_data.m_value.string, j, add_prefix, use_bjdata);
break;
}
@@ -25550,12 +25540,7 @@ class binary_writer
{
// a converted key must outlive the reference returned by sanitize_utf8_for_write
const object_key_string_t key_string = el.first;
string_t storage;
const string_t& key = sanitize_utf8_for_write(key_string, j, storage);
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
oa.write_characters(
reinterpret_cast<const CharType*>(key.data()),
key.size());
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);
}
@@ -26231,12 +26216,7 @@ class binary_writer
// a converted key must outlive the reference returned by sanitize_utf8_for_write
const object_key_string_t key_string = current.object_it->first;
string_t storage;
const string_t& key = sanitize_utf8_for_write(key_string, j, storage);
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
oa.write_characters(
reinterpret_cast<const CharType*>(key.data()),
key.size());
write_ubjson_text(key_string, j, false, use_bjdata);
const BasicJsonType* child = &(current.object_it->second);
const bool child_prefix = current.prefix_required;
++stack.back().object_it;
@@ -26721,10 +26701,13 @@ class binary_writer
bool write_bson_scalar(const string_t& name, const BasicJsonType& j)
{
const std::size_t name_size = name.size();
CharType* const out = oa.reserve(name_size + 2 + 8);
// a short string is written whole: its type, the name and its null
// byte, the length, the characters, and their null byte
const bool whole_string = j.is_string() && j.m_data.m_value.string->size() <= max_reserve_size - name_size - 7;
CharType* const out = oa.reserve(whole_string ? name_size + j.m_data.m_value.string->size() + 7 : name_size + 2 + 8);
// the name and its terminating null byte follow the type byte
CharType* p = out + 1;
std::memcpy(p, name.data(), name_size);
copy_characters(p, reinterpret_cast<const CharType*>(name.data()), name_size);
p += name_size;
*p++ = to_char_type(0x00);
@@ -26734,6 +26717,22 @@ class binary_writer
return true;
}
if (whole_string)
{
const string_t& value = *j.m_data.m_value.string;
out[0] = to_char_type(0x02);
store_little_endian<std::int32_t>(p, static_cast<std::int32_t>(value.size() + 1ul));
p += sizeof(std::int32_t);
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
p += value.size();
// the terminating null byte is written explicitly rather than
// taken from the buffer, so that string_t::data() need not be
// null-terminated
*p++ = to_char_type(0x00);
oa.commit(static_cast<std::size_t>(p - out));
return true;
}
if (j.is_string())
{
const string_t& value = *j.m_data.m_value.string;
@@ -27966,6 +27965,48 @@ class binary_writer
}
}
/*!
@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
With the default error handler (keep) and a string of up to 253 characters,
which has a length of the marker `i` or `U` and a single byte, everything is
written into space reserved from the sink in one go.
@param[in] value the string; checked or sanitized according to
@ref error_handler, with @a context as diagnostics context
@param[in] add_marker whether `S` is written first (not for an object key)
*/
void write_ubjson_text(const string_t& value, const BasicJsonType& context, const bool add_marker, const bool use_bjdata)
{
const std::size_t size = value.size();
if (JSON_HEDLEY_LIKELY(error_handler == error_handler_t::keep) && JSON_HEDLEY_LIKELY(size <= max_text_in_reservation - 1))
{
CharType* const out = oa.reserve(size + 3);
CharType* p = out;
if (add_marker)
{
*p++ = to_char_type('S');
}
*p++ = to_char_type(size <= 127 ? 'i' : 'U');
*p++ = to_char_type(static_cast<std::uint8_t>(size));
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), size);
oa.commit(static_cast<std::size_t>(p - out) + size);
return;
}
string_t storage;
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
if (add_marker)
{
oa.write_character(to_char_type('S'));
}
write_number_with_ubjson_prefix(sanitized.size(), true, use_bjdata);
oa.write_characters(
reinterpret_cast<const CharType*>(sanitized.data()),
sanitized.size());
}
/*!
@brief encode a UBJSON/BJData float, with its marker if @a add_prefix
@param[in] out where the encoding goes; at most 9 characters are written