mirror of
https://github.com/nlohmann/json.git
synced 2026-10-23 14:28:54 +00:00
serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891)
1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
2 files changed
+73
-71
No files matched your search
@@ -966,6 +966,28 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// write out the bytes pending in @ref string_buffer and reset @a bytes
|
||||
JSON_HEDLEY_ALWAYS_INLINE void emit_pending(std::size_t& bytes)
|
||||
{
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// write buffer and reset index if fewer than 13 bytes are left, as this
|
||||
/// is the maximal number of bytes to be written ("\uxxxx\uxxxx\0") for one
|
||||
/// code point
|
||||
JSON_HEDLEY_ALWAYS_INLINE void reserve_string_buffer(std::size_t& bytes)
|
||||
{
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief worker for @ref dump_escaped
|
||||
|
||||
@@ -1024,11 +1046,7 @@ class serializer
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order, then write the run directly
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
emit_pending(bytes);
|
||||
put_run(s, i, run);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
@@ -1049,11 +1067,10 @@ class serializer
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
emit_pending(bytes);
|
||||
// deliberately not put_buffer(): a fixed 8-byte memcpy
|
||||
// has no asserts and no write-through branch, which
|
||||
// matters on the hot path
|
||||
const auto& escape = ascii_escapes()[data[i]];
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
||||
{
|
||||
@@ -1128,14 +1145,7 @@ class serializer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
// remember the byte position of this accept
|
||||
bytes_after_last_accept = bytes;
|
||||
@@ -1187,14 +1197,7 @@ class serializer
|
||||
string_buffer[bytes++] = '\xBD';
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
}
|
||||
@@ -1227,14 +1230,7 @@ class serializer
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
// written ("\uxxxx\uxxxx\0") for one code point
|
||||
if (string_buffer.size() - bytes < 13)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
reserve_string_buffer(bytes);
|
||||
|
||||
bytes_after_last_accept = bytes;
|
||||
undumped_chars = 0;
|
||||
@@ -1600,30 +1596,17 @@ class serializer
|
||||
*/
|
||||
static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 256> copyable =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10
|
||||
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0
|
||||
}
|
||||
};
|
||||
// constant-initialized from the predicate: no runtime loop, no guard
|
||||
static constexpr std::array<std::uint8_t, 256> copyable = make_ascii_copyable(make_index_sequence<256> {});
|
||||
return copyable;
|
||||
}
|
||||
|
||||
template<std::size_t... Is>
|
||||
static constexpr std::array<std::uint8_t, 256> make_ascii_copyable(index_sequence<Is...> /*unused*/) noexcept
|
||||
{
|
||||
return {{ static_cast<std::uint8_t>(is_ascii_copyable(static_cast<unsigned char>(Is)) ? 1 : 0)... }};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the JSON form of each character below 0x60
|
||||
|
||||
@@ -1752,7 +1735,6 @@ class serializer
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
||||
{
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
std::size_t i = 0;
|
||||
while (i < n && data[i] >= 0x80)
|
||||
{
|
||||
@@ -1789,26 +1771,21 @@ class serializer
|
||||
char* d = write_buffer.data() + write_buffer_pos;
|
||||
if (codepoint > 0xFFFFu)
|
||||
{
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
||||
}
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
|
||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint));
|
||||
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
||||
i += length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
|
||||
static char* put_u_escape(char* d, const std::uint16_t codeunit) noexcept
|
||||
{
|
||||
const auto unit = static_cast<std::uint32_t>(codeunit);
|
||||
d[0] = '\\';
|
||||
d[1] = 'u';
|
||||
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||
d[5] = nibble_to_hex[unit & 0x0Fu];
|
||||
return d + 6;
|
||||
return hex4_lower(codeunit, d + 2);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1817,7 +1794,7 @@ class serializer
|
||||
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||
* lookup table, avoiding the format-string parsing and locale machinery of
|
||||
* lookup table (@ref hex4_lower), avoiding the format-string parsing and locale machinery of
|
||||
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||
*
|
||||
* @param[in] pos position in @a string_buffer to write at; there must
|
||||
@@ -1827,13 +1804,9 @@ class serializer
|
||||
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||
{
|
||||
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||
string_buffer[pos + 0] = '\\';
|
||||
string_buffer[pos + 1] = 'u';
|
||||
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
||||
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
||||
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
||||
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
||||
hex4_lower(codeunit, &string_buffer[pos + 2]);
|
||||
pos += 6;
|
||||
}
|
||||
|
||||
|
||||
@@ -2042,3 +2042,32 @@ TEST_CASE("string scanning kernels")
|
||||
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
||||
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
||||
}
|
||||
|
||||
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
|
||||
{
|
||||
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
|
||||
// second time; check every byte value at every position of a 24-byte buffer
|
||||
// (three words) and for several lengths, with the byte at the end or inside
|
||||
for (unsigned int value = 0; value < 256; ++value)
|
||||
{
|
||||
for (std::size_t position = 0; position < 24; ++position)
|
||||
{
|
||||
std::array<unsigned char, 24> buffer{};
|
||||
buffer.fill(static_cast<unsigned char>('a'));
|
||||
buffer[position] = static_cast<unsigned char>(value);
|
||||
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
|
||||
for (const std::size_t n : {position, position + 1, std::size_t{24}})
|
||||
{
|
||||
if (n > 24)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::size_t expected = (copyable || n <= position) ? n : position;
|
||||
CAPTURE(value)
|
||||
CAPTURE(position)
|
||||
CAPTURE(n)
|
||||
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user