mirror of
https://github.com/nlohmann/json.git
synced 2026-10-16 03:07:12 +00:00
Scan strings with SSE2, SSSE3, or NEON
The string fast path found the end of a run of plain characters 8 bytes at a time (SWAR) and validated non-ASCII text one sequence at a time, calling validate_one_utf8() out of line for each. The vector code of json_view's scanner, in the new shared header detail/input/simd_scan.hpp, now does both 16 bytes at a time: SSE2 finds the end of a run on x86-64, and the UTF-8 check of Keiser and Lemire runs with NEON on AArch64 and with SSSE3 on x86-64. SSSE3 is not part of x86-64, so the check is compiled for it with a function attribute and used when the CPU has it (cached in a constant-initialized atomic), else the scalar check; code compiled with -mssse3 skips the CPU check. The first word of a run is still checked with SWAR (the first four with NEON, where a vector mask costs more), as most keys and short values end there, and the scalar helpers are always inlined. On anything else, the scalar code is unchanged. The new macro JSON_NO_SIMD selects the portable code everywhere, including the SSE2/NEON digit formatting of the floating-point writer. The lexer tests now also run with JSON_NO_SIMD and, on x86-64, with -mssse3, and a new test places every byte that ends a run at each offset across several 16-byte blocks, after ASCII and after CJK text. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
15 files changed
+1072
-39
No files matched your search
@@ -716,6 +716,73 @@ TEST_CASE("lexer string fast path")
|
||||
CAPTURE(mismatches)
|
||||
CHECK(mismatches.empty());
|
||||
}
|
||||
|
||||
SECTION("special bytes at every offset of the vector blocks")
|
||||
{
|
||||
// With SSE2 or NEON, runs are scanned 16 bytes at a time (after one
|
||||
// SWAR word, or four with NEON), and runs of non-ASCII text are
|
||||
// checked 16 bytes at a time as well. Place every kind of byte that
|
||||
// ends a run at each offset across several blocks, after ASCII and
|
||||
// after CJK text, so sequences also straddle the block boundaries.
|
||||
// The reference is a std::list, which has no contiguous storage and
|
||||
// is therefore read one character at a time.
|
||||
const auto byte_path = [](const std::string & doc) -> std::string
|
||||
{
|
||||
const std::list<char> chars(doc.begin(), doc.end());
|
||||
JSON_TRY
|
||||
{
|
||||
return json::parse(chars.begin(), chars.end()).dump();
|
||||
}
|
||||
JSON_CATCH (const json::exception & e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
};
|
||||
const std::vector<std::string> specials =
|
||||
{
|
||||
"\"", "\\", "\\u00e9", bytes({0x01}), bytes({0x1F}), bytes({0x7F}),
|
||||
bytes({0xC3, 0xA9}), bytes({0xE4, 0xB8, 0xAD}), bytes({0xF0, 0x9F, 0x98, 0x80}),
|
||||
bytes({0xFF}), bytes({0xC3}), bytes({0xE4, 0xB8}), bytes({0xED, 0xA0, 0x80}), bytes({0xC0, 0x80})
|
||||
};
|
||||
const std::string cjk = bytes({0xE4, 0xB8, 0xAD}); // U+4E2D
|
||||
|
||||
std::vector<std::string> mismatches;
|
||||
for (std::size_t offset = 0; offset <= 80; ++offset)
|
||||
{
|
||||
std::string text;
|
||||
while (text.size() < offset)
|
||||
{
|
||||
text += cjk;
|
||||
}
|
||||
for (const auto& prefix :
|
||||
{
|
||||
std::string(offset, 'a'), text
|
||||
})
|
||||
{
|
||||
for (const auto& special : specials)
|
||||
{
|
||||
// the closing quote right behind, or in a later block
|
||||
for (const int tail :
|
||||
{
|
||||
0, 1, 3, 20
|
||||
})
|
||||
{
|
||||
std::string doc = "[\"";
|
||||
doc += prefix;
|
||||
doc += special;
|
||||
doc.append(static_cast<std::size_t>(tail), 'b');
|
||||
doc += "\"]";
|
||||
if (outcome(doc, false) != byte_path(doc))
|
||||
{
|
||||
mismatches.push_back(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CAPTURE(mismatches)
|
||||
CHECK(mismatches.empty());
|
||||
}
|
||||
#endif
|
||||
|
||||
// json::accept() never throws, so the ranges stay covered without exceptions
|
||||
|
||||
Reference in new issue
Block a user