mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 11:17:13 +00:00
Keep the string scan from slowing down short strings and dump() on Arm
Three benchmarks were 7-9% slower than develop on Apple M1 (Apple Clang, -O3 -flto): Accept/jeopardy, Dump/citm_catalog, and Dump/jeopardy. - dump(): the serializer passes a string without its closing quote, so plain_run_end() ran out of input and called the out-of-line NEON loop for every string, with fewer than 16 bytes left. The loop is now only called for 16 bytes or more. - accept(): no string of jeopardy.json reaches the vector UTF-8 check, yet compiling it into the lexer's scan function made it 5% slower: the function runs once per token and now saved more registers and kept a stack slot. lexer_string_bulk_run() checks the first four words inline and continues out of line, and the serializer keeps string_bulk_run() inline, which is faster there. - The byte that ends a run is read from the scanned buffer instead of through get(), which Apple Clang merged with the other get() calls that load the read position again right after it was stored (3%). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+158
-46
No files matched your search
@@ -35,7 +35,7 @@
|
||||
// This file contains the byte-level string-scanning helpers used by the lexer's
|
||||
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
||||
// lexer's template parameters) so they are free functions, keeping the lexer
|
||||
// itself focused on the state machine; see lexer::scan_string_bulk().
|
||||
// itself focused on the state machine; see lexer::get_after_string_run().
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -311,12 +311,19 @@ JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char
|
||||
}
|
||||
p += 8;
|
||||
}
|
||||
p = vector_plain_run_long(p, e);
|
||||
if (e - p < 16) // else the vector loop stopped at a special byte
|
||||
// the vector loop only for 16 bytes or more: the serializer passes a
|
||||
// string without its closing quote, so most of its runs end at e, and a
|
||||
// call of the out-of-line NEON loop for a shorter rest made dump() up to
|
||||
// 10% slower
|
||||
if (e - p >= 16)
|
||||
{
|
||||
p += find_string_special(p, static_cast<std::size_t>(e - p));
|
||||
p = vector_plain_run_long(p, e);
|
||||
if (e - p >= 16)
|
||||
{
|
||||
return p; // the vector loop stopped at a special byte
|
||||
}
|
||||
}
|
||||
return p;
|
||||
return p + find_string_special(p, static_cast<std::size_t>(e - p));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -418,6 +425,42 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data,
|
||||
return scalar_string_bulk_run(data, n);
|
||||
}
|
||||
|
||||
#if JSON_SCAN_NEON
|
||||
/// scalar_string_bulk_run() from @a start on, out of line (see
|
||||
/// lexer_string_bulk_run())
|
||||
JSON_HEDLEY_NEVER_INLINE inline std::size_t string_bulk_run_from(const unsigned char* data, std::size_t start, std::size_t n) noexcept
|
||||
{
|
||||
return start + scalar_string_bulk_run(data + start, n - start);
|
||||
}
|
||||
#endif
|
||||
|
||||
// string_bulk_run() for the lexer. With NEON, only the first four words are
|
||||
// checked inline (most keys and short values end there), and longer runs and
|
||||
// non-ASCII text continue out of line. The lexer's scan function runs once per
|
||||
// token, and the vector code inlined into it needs more registers, which every
|
||||
// token pays for: 5% of json::accept() on jeopardy.json with Apple Clang on
|
||||
// Apple M1, even though no string of that file reaches the vector code. The
|
||||
// serializer keeps string_bulk_run() inline, which is faster there.
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t lexer_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
||||
std::size_t i = 0;
|
||||
for (int words = 4; words > 0 && n - i >= 8; --words)
|
||||
{
|
||||
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
||||
if (special != 0)
|
||||
{
|
||||
const std::size_t pos = i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||
return data[pos] < 0x80u ? pos : string_bulk_run_from(data, pos, n);
|
||||
}
|
||||
i += 8;
|
||||
}
|
||||
return string_bulk_run_from(data, i, n);
|
||||
#else
|
||||
return string_bulk_run(data, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
||||
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
||||
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||
|
||||
Reference in new issue
Block a user