json_view: skip the digits of a fraction 16 bytes at a time (SSE2)
The digits after the decimal point were skipped by skip_digits(), 16 compare-and-branch steps in a row: about 3.5 instructions per digit, and an exit branch that is mispredicted when the lengths of the fractions vary. On x86 with SSE2, the fraction is now skipped by skip_fraction_digits(): 16 bytes are compared at once and the end of the run is taken from the bit mask, which is about a third of the instructions for a fraction of 12 digits. The last bytes of the input are skipped one at a time as before. Where there is no SSE2 (AArch64, other targets, JSON_NO_SIMD) skip_fraction_digits() is skip_digits(): nothing changes there. Integer digits keep skip_digits(), whose exit is predictable. GCC unrolls the loop over the last bytes in every inlined copy of the new function, which made the parse loop 4 KiB larger and parse() of documents with hardly any fractions about 4 % slower. For GCC that loop is therefore out of line (skip_digits_tail()); Clang is faster with it inline. json_document::parse(), A/B in one binary, 6 code layouts, x86-64 Haswell, time after / before (GCC 16.2 / Clang 23): - numbers.json (10,001 floats): 0.715 / 0.680 - canada.json: 0.778 / 0.790 - mesh.json: 0.908 / 0.883 - marine_ik.json: 0.937 / 0.926 - mesh.pretty.json: 0.923 / 0.960 Files with few or no fractions and files with fractions of a fixed length (20,000 prices with two decimals, 60,000 coordinates with six): between 0.98 and 1.014 on both compilers, except citm_catalog.json with GCC (1.05: the instruction count is +0.3 %, and that row varies by +-7 % between builds of unrelated variants on this machine). New test: fractions of every length from 0 to 40 digits followed by every kind of byte that ends a number or lies next to the digits in ASCII, in several positions, and cut in every place near the end of the text, against json::parse() (the length of the number is part of what is compared), for all option combinations and with and without a terminating NUL. Checked on x86-64 with GCC 16.2, Clang 23 and Apple clang under Rosetta, and with a deliberate off-by-one at the 16th byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
4 files changed
+145
-2
No files matched your search
@@ -523,6 +523,47 @@ TEST_CASE("json_view builder: strings across vector blocks")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("json_view builder: fraction digits")
|
||||
{
|
||||
// The digits of a fraction are skipped 16 bytes at a time where there are
|
||||
// that many left (SSE2), and one at a time after that. Fractions of every
|
||||
// length from 0 to 40 digits, followed by every kind of byte that ends a
|
||||
// number or lies next to the digits in ASCII ('/' and ':'), as values, as
|
||||
// keys' neighbours, and at the very end of the text (a vector without a
|
||||
// terminating NUL). The length of the number is part of the value that
|
||||
// the test compares with json::parse().
|
||||
const std::string followers = ",]}/:.eE+-x \n\t\"09";
|
||||
std::mt19937 rng(2028); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
|
||||
for (std::size_t digits = 0; digits <= 40; ++digits)
|
||||
{
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
std::string fraction;
|
||||
for (std::size_t k = 0; k < digits; ++k)
|
||||
{
|
||||
fraction += static_cast<char>('0' + (rng() % 10));
|
||||
}
|
||||
const std::string number = std::string(rng() % 2 == 0 ? "-" : "") + std::to_string(rng() % 100000) + "." + fraction;
|
||||
check_same(number);
|
||||
check_same("[" + number + "]");
|
||||
check_same("[" + number + "," + number + "]");
|
||||
check_same("{\"a\":" + number + "}");
|
||||
for (const char follower : followers)
|
||||
{
|
||||
check_same(number + follower);
|
||||
check_same("[" + number + follower + "]");
|
||||
check_same("[" + number + follower + std::string(rng() % 20, '1'));
|
||||
}
|
||||
// everything near the end of the text: the number cut in every place
|
||||
for (std::size_t cut = 1; cut <= 18 && cut <= number.size(); ++cut)
|
||||
{
|
||||
check_same(number.substr(0, number.size() - cut));
|
||||
check_same("[" + number.substr(0, number.size() - cut));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("json_view builder: escapes between runs of text")
|
||||
{
|
||||
// The text between escapes is copied 16 bytes at a time where there are
|
||||
|
||||
Reference in new issue
Block a user