Copy short printable-ASCII strings without starting the scanner (#5878)
dump() now checks strings of up to 32 bytes against a table of the bytes that can be written verbatim, four bytes per step, and copies them in one go if all of them can. That is cheaper than setting up the vector scanner, whose setup costs more than such a string takes to scan; SQLite's jsonAppendString() writes strings with the same kind of table loop. Most object keys and many values are this short (citm_catalog averages 8 bytes per string, jeopardy 14). GCC 16 and 13 stopped inlining put_indent() and put_literal() into the pretty-printing loops once dump_escaped() had grown, which made indented output up to 13% slower; both are now always inlined. The new test puts every ASCII character and a 2-byte character at every position of strings of 1 to 40 bytes and compares the output with the reference implementation of the existing UTF-8 tests. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
52fecc1fab
commit
6aefd8c662
3 files changed
+153
-4
No files matched your search
@@ -693,6 +693,33 @@ class serializer
|
||||
*/
|
||||
void dump_escaped(const string_t& s)
|
||||
{
|
||||
// Short strings of printable ASCII - most object keys and many values -
|
||||
// are checked byte by byte against a table and then copied in one go.
|
||||
// For them, this is cheaper than starting the vector scanner below,
|
||||
// whose setup costs more than such a string takes to scan. The same
|
||||
// check serves both values of ensure_ascii.
|
||||
if (s.size() <= 32)
|
||||
{
|
||||
const auto& copyable = ascii_copyable();
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
const std::size_t n = s.size();
|
||||
std::size_t k = 0;
|
||||
while (k + 4 <= n && copyable[data[k]] != 0 && copyable[data[k + 1]] != 0
|
||||
&& copyable[data[k + 2]] != 0 && copyable[data[k + 3]] != 0)
|
||||
{
|
||||
k += 4;
|
||||
}
|
||||
while (k < n && copyable[data[k]] != 0)
|
||||
{
|
||||
++k;
|
||||
}
|
||||
if (k == n)
|
||||
{
|
||||
put_string(s, 0, n);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch once here rather than test the flag inside the loop: it does
|
||||
// not change while a string is written, and folding it lets each of the
|
||||
// two scanners be inlined into a loop of its own
|
||||
@@ -1073,8 +1100,12 @@ class serializer
|
||||
the indentation character once and flushing that same content repeatedly:
|
||||
flushing does not disturb what the buffer holds, so re-filling it between
|
||||
flushes would be redundant work.
|
||||
|
||||
@note inlined on purpose, as is @ref put_literal: GCC otherwise stops
|
||||
inlining both into the pretty-printing loops once @ref dump_escaped
|
||||
has grown, which made indented output up to 13% slower
|
||||
*/
|
||||
void put_indent(std::size_t indent)
|
||||
JSON_HEDLEY_ALWAYS_INLINE void put_indent(std::size_t indent)
|
||||
{
|
||||
// closing braces at the outermost level ask for no indentation at all
|
||||
if (indent == 0)
|
||||
@@ -1124,7 +1155,7 @@ class serializer
|
||||
write-through path for oversized runs.
|
||||
*/
|
||||
template<std::size_t N>
|
||||
void put_literal(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
JSON_HEDLEY_ALWAYS_INLINE void put_literal(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
static_assert(N >= 2, "put_literal expects a non-empty string literal");
|
||||
// the array bound counts the terminating NUL, which is not written
|
||||
@@ -1233,6 +1264,39 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether each byte can be written verbatim under either value of ensure_ascii
|
||||
|
||||
Entry c is 1 for printable ASCII other than '"' and '\\' (see
|
||||
is_ascii_copyable()) and 0 otherwise. A table lookup per byte is the
|
||||
cheapest test for the short strings handled first in dump_escaped().
|
||||
*/
|
||||
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
|
||||
}
|
||||
};
|
||||
return copyable;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the JSON form of each character below 0x60
|
||||
|
||||
|
||||
Reference in new issue
Block a user