mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 19:18:12 +00:00
Merge branch 'perf/lexer-dispatch' into perf/dom-move-keys
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+158
-46
No files matched your search
@@ -418,30 +418,43 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
/// well-formed UTF-8 sequences starting at the current read position, then
|
||||||
/// the first byte that needs individual handling (the closing quote, an
|
/// read the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||||
void scan_string_bulk(std::true_type /*bulk*/)
|
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||||
|
/// Apple Clang merged this read with the other get() calls of the scan
|
||||||
|
/// function, which load the read position again right after it was stored
|
||||||
|
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||||
|
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// a pending unget must be consumed through the normal path first
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
const auto* const raw = ia.bulk_data();
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(raw);
|
||||||
|
|
||||||
const std::size_t pos = string_bulk_run(data, remaining);
|
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||||
if (pos == 0)
|
if (pos != 0)
|
||||||
{
|
{
|
||||||
return;
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
}
|
}
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
if (pos == remaining)
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
{
|
||||||
|
return get(); // end of the contiguous input
|
||||||
|
}
|
||||||
|
// what get() does, with the byte at hand
|
||||||
|
advance_position();
|
||||||
|
ia.bulk_skip(1);
|
||||||
|
current = char_traits<char_type>::to_int_type(raw[pos]);
|
||||||
|
return track_after_read();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||||
@@ -474,7 +487,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief scan a string literal
|
@brief scan a string literal
|
||||||
@@ -502,11 +518,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// handle the next special byte through the switch below
|
// get the next character, the one that ends the run
|
||||||
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
||||||
|
|
||||||
// get the next character
|
|
||||||
switch (get())
|
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
// This file contains the byte-level string-scanning helpers used by the lexer's
|
// 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
|
// 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
|
// 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
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -311,12 +311,19 @@ JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char
|
|||||||
}
|
}
|
||||||
p += 8;
|
p += 8;
|
||||||
}
|
}
|
||||||
p = vector_plain_run_long(p, e);
|
// the vector loop only for 16 bytes or more: the serializer passes a
|
||||||
if (e - p < 16) // else the vector loop stopped at a special byte
|
// 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
|
#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);
|
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`
|
// 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
|
// 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
|
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||||
|
|||||||
Generated
+79
-23
@@ -11494,7 +11494,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
// This file contains the byte-level string-scanning helpers used by the lexer's
|
// 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
|
// 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
|
// 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
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -11770,12 +11770,19 @@ JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char
|
|||||||
}
|
}
|
||||||
p += 8;
|
p += 8;
|
||||||
}
|
}
|
||||||
p = vector_plain_run_long(p, e);
|
// the vector loop only for 16 bytes or more: the serializer passes a
|
||||||
if (e - p < 16) // else the vector loop stopped at a special byte
|
// 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
|
#endif
|
||||||
|
|
||||||
@@ -11877,6 +11884,42 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data,
|
|||||||
return scalar_string_bulk_run(data, n);
|
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`
|
// 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
|
// 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
|
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||||
@@ -12323,30 +12366,43 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
/// well-formed UTF-8 sequences starting at the current read position, then
|
||||||
/// the first byte that needs individual handling (the closing quote, an
|
/// read the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||||
void scan_string_bulk(std::true_type /*bulk*/)
|
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||||
|
/// Apple Clang merged this read with the other get() calls of the scan
|
||||||
|
/// function, which load the read position again right after it was stored
|
||||||
|
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||||
|
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// a pending unget must be consumed through the normal path first
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
const auto* const raw = ia.bulk_data();
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(raw);
|
||||||
|
|
||||||
const std::size_t pos = string_bulk_run(data, remaining);
|
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||||
if (pos == 0)
|
if (pos != 0)
|
||||||
{
|
{
|
||||||
return;
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
}
|
}
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
if (pos == remaining)
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
{
|
||||||
|
return get(); // end of the contiguous input
|
||||||
|
}
|
||||||
|
// what get() does, with the byte at hand
|
||||||
|
advance_position();
|
||||||
|
ia.bulk_skip(1);
|
||||||
|
current = char_traits<char_type>::to_int_type(raw[pos]);
|
||||||
|
return track_after_read();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||||
@@ -12379,7 +12435,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief scan a string literal
|
@brief scan a string literal
|
||||||
@@ -12407,11 +12466,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// handle the next special byte through the switch below
|
// get the next character, the one that ends the run
|
||||||
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
||||||
|
|
||||||
// get the next character
|
|
||||||
switch (get())
|
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
|
|||||||
Reference in new issue
Block a user