mirror of
https://github.com/nlohmann/json.git
synced 2026-10-22 14:08:13 +00:00
Write the bytes of an unoptimized UBJSON and BON8 binary value in blocks
Without use_type, a binary value is an array of its bytes, and was written one sink call per marker and per byte (UBJSON, BJData), or through the integer ladder for each byte (BON8). The bytes are now encoded in blocks into space reserved from the sink, by a function that is not inlined. BON8 stores both bytes of an integer above 39 and keeps the second only if it is needed, so that the code does not branch on the value. Instructions per call for a 1 MB binary value (Linux, GCC 13, thousands): to_ubjson 34253 -> 9521, to_bjdata 34253 -> 9521, to_bon8 53088 -> 17333. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
2 files changed
+148
-48
No files matched your search
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // reverse
|
||||
#include <algorithm> // min, reverse
|
||||
#include <array> // array
|
||||
#include <cmath> // isnan, isinf
|
||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
||||
@@ -694,13 +694,7 @@ class binary_writer
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(bjdata_draft3 ? 'B' : 'U'));
|
||||
// the cast is needed for binary types whose value type
|
||||
// is not an integer (e.g., std::byte)
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(j.m_data.m_value.binary->data()[i])));
|
||||
}
|
||||
write_ubjson_binary_bytes(*j.m_data.m_value.binary, bjdata_draft3);
|
||||
}
|
||||
|
||||
if (!use_count)
|
||||
@@ -3010,6 +3004,35 @@ class binary_writer
|
||||
/// space for the largest encoding of encode_ubjson_integer(): the marker and 8 bytes
|
||||
static constexpr std::size_t ubjson_integer_encoding_space = 9;
|
||||
|
||||
/*!
|
||||
@brief write the bytes of a binary value as an array of UBJSON uint8 values
|
||||
|
||||
Every byte is written with its marker, in blocks that fit the space reserved
|
||||
from the sink. Kept out of line, so that write_ubjson does not carry the
|
||||
registers of its loop.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_ubjson_binary_bytes(const binary_t& binary, const bool bjdata_draft3)
|
||||
{
|
||||
const CharType marker = to_char_type(bjdata_draft3 ? 'B' : 'U');
|
||||
const auto* const data = binary.data();
|
||||
const std::size_t size = binary.size();
|
||||
for (std::size_t i = 0; i < size;)
|
||||
{
|
||||
const std::size_t block = (std::min)(size - i, max_reserve_size / 2);
|
||||
CharType* const out = oa.reserve(2 * block);
|
||||
for (std::size_t k = 0; k < block; ++k)
|
||||
{
|
||||
out[2 * k] = marker;
|
||||
// the cast is needed for binary types whose value type
|
||||
// is not an integer (e.g., std::byte)
|
||||
out[(2 * k) + 1] = to_char_type(static_cast<std::uint8_t>(data[i + k]));
|
||||
}
|
||||
i += block;
|
||||
oa.commit(2 * block);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@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
|
||||
@@ -3748,22 +3771,7 @@ class binary_writer
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
// BON8 has no binary type: write the bytes as an array of
|
||||
// integers, like UBJSON and BJData do
|
||||
const auto N = j.m_data.m_value.binary->size();
|
||||
write_bon8_marker(static_cast<std::uint8_t>(N <= 4 ? 0x80 + N : 0x85), string_open);
|
||||
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
{
|
||||
// the cast is needed for binary types whose value type
|
||||
// is not an integer (e.g., std::byte)
|
||||
write_bon8_integer(static_cast<std::uint8_t>(j.m_data.m_value.binary->data()[i]));
|
||||
}
|
||||
|
||||
if (N > 4)
|
||||
{
|
||||
write_bon8_marker(0xFE, string_open);
|
||||
}
|
||||
write_bon8_binary(*j.m_data.m_value.binary, string_open);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3850,6 +3858,48 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a binary value as BON8
|
||||
|
||||
BON8 has no binary type: the bytes are written as an array of integers,
|
||||
like UBJSON and BJData do. Kept out of line, so that write_bon8_value does
|
||||
not carry the registers of its loop.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_bon8_binary(const binary_t& binary, bool& string_open)
|
||||
{
|
||||
const auto N = binary.size();
|
||||
write_bon8_marker(static_cast<std::uint8_t>(N <= 4 ? 0x80 + N : 0x85), string_open);
|
||||
|
||||
// a byte is an integer of 0 to 255: one byte up to 39, two bytes above
|
||||
const auto* const data = binary.data();
|
||||
for (std::size_t i = 0; i < N;)
|
||||
{
|
||||
const std::size_t block = (std::min)(N - i, max_reserve_size / 2);
|
||||
CharType* const start = oa.reserve(2 * block);
|
||||
CharType* out = start;
|
||||
for (std::size_t k = 0; k < block; ++k, ++i)
|
||||
{
|
||||
// the cast is needed for binary types whose value type
|
||||
// is not an integer (e.g., std::byte)
|
||||
const auto value = static_cast<unsigned>(static_cast<std::uint8_t>(data[i]));
|
||||
// both bytes are stored, the second only counts for a value above 39:
|
||||
// 0xC2 + ((value - 40) >> 7), (value - 40) & 0x7F
|
||||
const unsigned wide = value - 40u;
|
||||
const bool is_wide = value > 39u;
|
||||
out[0] = to_char_type(static_cast<std::uint8_t>(is_wide ? 0xC2u + (wide >> 7u) : 0x90u + value));
|
||||
out[1] = to_char_type(static_cast<std::uint8_t>(wide & 0x7Fu));
|
||||
out += is_wide ? 2 : 1;
|
||||
}
|
||||
oa.commit(static_cast<std::size_t>(out - start));
|
||||
}
|
||||
|
||||
if (N > 4)
|
||||
{
|
||||
write_bon8_marker(0xFE, string_open);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a single byte that is not part of a string
|
||||
|
||||
|
||||
Reference in new issue
Block a user