Scan the strings of json_view's dump() with ensure_ascii 16 bytes at a time
With ensure_ascii, a string of the source that has a byte >= 0x7F was written by a generic path: put() per piece and a validate-and-decode step for every character beyond ASCII. It now goes through a writer with the cursor in a local variable. The part of the string that has nothing to escape is found 16 bytes at a time (SSE2 or NEON; the existing 8-byte scan without vector support) and copied; strings that are ASCII throughout are still decided by the 8-byte check of dump() with ensure_ascii (a 16-byte scan for them was 2-5 % slower for short strings). The two-byte sequences (lead C2..DF) and the three-byte sequences whose continuation bytes may have any value (lead E1..EF except ED) are decoded with a few compares; E0, ED, four-byte sequences, and damaged input go through validate_one_utf8() as before, so damaged images still throw type_error.316. dump(-1, ' ', true) instructions per call, macOS arm64 (develop -> now); the compact dump without ensure_ascii and dumps of ASCII text are unchanged: twitter 5.42M -> 3.57M citm 3.28M -> 3.24M jeopardy(4k) 5.78M -> 5.65M CJK text, raw 66.5M -> 35.3M escaped \u 32.7M -> 17.0M Latin text with accents 11.5M -> 9.4M ASCII text 4.40M -> 4.40M Linux instructions (GCC 13 / Clang 18), dump(-1, ' ', true): twitter -9 % / -33 % CJK text -14 % / -47 % Latin text -8 % / -18 % citm -0.2 % / -0.4 % ASCII text -0.1 % / -0.3 % The compact dump without ensure_ascii is unchanged (canada 0.0 %). The unit test covers every character of the Basic Multilingual Plane, and a character that needs an escape at each position of strings of 0..40 bytes. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+519
-144
No files matched your search
@@ -186,6 +186,100 @@ TEST_CASE("json_view dump")
|
||||
CHECK(deep_document.root().dump() == deep);
|
||||
}
|
||||
|
||||
SECTION("ensure_ascii: a character that needs an escape at each position of a string")
|
||||
{
|
||||
// string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text
|
||||
for (const char* const special :
|
||||
{
|
||||
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\"
|
||||
})
|
||||
{
|
||||
for (std::size_t length = 0; length <= 40; ++length)
|
||||
{
|
||||
for (std::size_t position = 0; position <= length; ++position)
|
||||
{
|
||||
std::string content(length, 'a');
|
||||
content.insert(position, special);
|
||||
const std::string texts[] =
|
||||
{
|
||||
"\"" + content + "\"",
|
||||
"[\"" + content + "\"]",
|
||||
"{\"" + content + "\":1}",
|
||||
};
|
||||
for (const std::string& text : texts)
|
||||
{
|
||||
CAPTURE(text)
|
||||
const ordered_json_document d = ordered_json_document::parse(text);
|
||||
const ordered_json j = ordered_json::parse(text);
|
||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||
CHECK(d.root().dump() == j.dump());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond")
|
||||
{
|
||||
// (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF)
|
||||
std::string text = "[";
|
||||
std::size_t in_chunk = 0;
|
||||
const auto append = [&](std::uint32_t cp)
|
||||
{
|
||||
if (cp < 0x80)
|
||||
{
|
||||
if (cp < 0x20 || cp == '"' || cp == '\\')
|
||||
{
|
||||
return;
|
||||
}
|
||||
text += static_cast<char>(cp);
|
||||
}
|
||||
else if (cp < 0x800)
|
||||
{
|
||||
text += static_cast<char>(0xC0 | (cp >> 6));
|
||||
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||
}
|
||||
else if (cp < 0x10000)
|
||||
{
|
||||
text += static_cast<char>(0xE0 | (cp >> 12));
|
||||
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||
}
|
||||
else
|
||||
{
|
||||
text += static_cast<char>(0xF0 | (cp >> 18));
|
||||
text += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
|
||||
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
|
||||
text += static_cast<char>(0x80 | (cp & 0x3F));
|
||||
}
|
||||
};
|
||||
for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp)
|
||||
{
|
||||
if (cp >= 0xD800 && cp < 0xE000)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (in_chunk == 0)
|
||||
{
|
||||
text += text.size() > 1 ? ",\"" : "\"";
|
||||
}
|
||||
append(cp);
|
||||
if (++in_chunk == 37)
|
||||
{
|
||||
text += "\"";
|
||||
in_chunk = 0;
|
||||
}
|
||||
}
|
||||
text += in_chunk != 0 ? "\"]" : "]";
|
||||
const ordered_json_document d = ordered_json_document::parse(text);
|
||||
const ordered_json j = ordered_json::parse(text);
|
||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
|
||||
CHECK(d.root().dump() == j.dump());
|
||||
CHECK(d.root().dump(2) == j.dump(2));
|
||||
}
|
||||
|
||||
SECTION("deep nesting, indented")
|
||||
{
|
||||
// indentations of up to and beyond the width of the writer's fixed-size copy
|
||||
|
||||
Reference in new issue
Block a user