mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 06:30:31 +00:00
Format the digits of numbers in vector registers and without reloads
- zmij::to_shortest() keeps the last digit apart from the 15 or 16 digits before it, and dtoa_impl::write_shortest() converts those at once: with SSE2 on x86-64 and NEON on 64-bit Arm (no CPU check needed), else eight digits at a time. The decimal point is inserted in the register; reading the digits back right after storing them stalled store forwarding. - json::dump() writes floats and integers straight into its write buffer instead of copying them from number_buffer, and integers below 10^16 eight digits at a time. - json_view's dump() writes doubles from their bits and tokens of up to 15 digits through the same code; its own NEON writer is removed. to_chars() on canada.json: 35 -> 29 ns per double (x86-64), 18.8 -> 14.2 ns (Apple M1); json::dump() of canada.json 16-29% faster, of citm_catalog.json 14-19%. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -5385,8 +5385,6 @@ NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
// #include <nlohmann/detail/view/number.hpp>
|
||||
|
||||
// #include <nlohmann/detail/view/simd.hpp>
|
||||
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -5585,69 +5583,6 @@ struct dump_style
|
||||
bool source_numbers = false; ///< copy number tokens from the source
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief digits * 10^exp as dtoa_impl::write_decimal() writes it (digits not 0,
|
||||
at most 17 digits; up to 41 bytes are written at first)
|
||||
|
||||
With NEON, the fixed layouts ("0.00123", "12.5", "100.0") are put together in
|
||||
vector registers: output byte i is byte s + i of the digits (after '0's)
|
||||
before the point, and byte s + i - 1 after it. The portable code writes the
|
||||
digits to a buffer and copies them from there at another offset, and a load
|
||||
that spans several recent stores waits until they reach the cache.
|
||||
*/
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE char* write_decimal(char* first, std::uint64_t digits, int exp) noexcept
|
||||
{
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
namespace dtoa = ::nlohmann::detail::dtoa_impl;
|
||||
const std::uint64_t upper = digits / 100000000u;
|
||||
const std::uint64_t b0 = upper / 100000000u; // one digit
|
||||
const std::uint64_t b1 = dtoa::eight_digit_bytes(upper % 100000000u);
|
||||
const std::uint64_t b2 = dtoa::eight_digit_bytes(digits % 100000000u);
|
||||
// leading and trailing zero digits (as dtoa_impl::write_decimal())
|
||||
int leading = 7;
|
||||
if (b0 == 0)
|
||||
{
|
||||
leading = b1 != 0 ? 8 + (count_leading_zeros(b1) / 8) : 16 + (count_leading_zeros(b2) / 8);
|
||||
}
|
||||
int zeros = 16;
|
||||
if (b2 != 0)
|
||||
{
|
||||
zeros = count_trailing_zeros(b2) / 8;
|
||||
}
|
||||
else if (b1 != 0)
|
||||
{
|
||||
zeros = 8 + (count_trailing_zeros(b1) / 8);
|
||||
}
|
||||
const int k = 24 - leading - zeros; // significant digits
|
||||
const int n = k + exp + zeros; // position of the point after the first digit
|
||||
if (NLOHMANN_VIEW_LIKELY(-4 < n && n <= 15))
|
||||
{
|
||||
static const std::array<std::uint8_t, 32> iota = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}};
|
||||
const int pad = n <= 0 ? 1 - n : 0;
|
||||
const int len = k + pad;
|
||||
const int point = n + pad;
|
||||
// the 24 digit bytes in memory order, then '0's
|
||||
const std::uint64_t zero_chars = 0x3030303030303030u;
|
||||
const uint8x16x2_t table = {{
|
||||
vcombine_u8(vcreate_u8(__builtin_bswap64(b0 + zero_chars)), vcreate_u8(__builtin_bswap64(b1 + zero_chars))),
|
||||
vcombine_u8(vcreate_u8(__builtin_bswap64(b2 + zero_chars)), vdup_n_u8('0'))
|
||||
}
|
||||
};
|
||||
const uint8x16_t s = vdupq_n_u8(static_cast<std::uint8_t>(leading - pad));
|
||||
const uint8x16_t at_point = vdupq_n_u8(static_cast<std::uint8_t>(point));
|
||||
for (std::size_t half = 0; half < 2; ++half)
|
||||
{
|
||||
const uint8x16_t i = vld1q_u8(iota.data() + (16 * half));
|
||||
// (+ 0xFF is - 1 after the point; indexes past the digits read a '0')
|
||||
const uint8x16_t index = vminq_u8(vaddq_u8(vaddq_u8(i, s), vcgtq_u8(i, at_point)), vdupq_n_u8(31));
|
||||
vst1q_u8(reinterpret_cast<std::uint8_t*>(first) + (16 * half), vbslq_u8(vceqq_u8(i, at_point), vdupq_n_u8('.'), vqtbl2q_u8(table, index))); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
}
|
||||
return first + (point >= len ? point + 2 : len + 1); // "digits[000].0" ends after ".0"
|
||||
}
|
||||
#endif
|
||||
return ::nlohmann::detail::dtoa_impl::write_decimal(first, digits, exp);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a view's subtree as basic_json::dump() writes the value
|
||||
|
||||
@@ -6149,7 +6084,11 @@ class view_serializer
|
||||
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
|
||||
{
|
||||
*w = '-';
|
||||
return write_decimal(w + (d.negative ? 1 : 0), d.w, static_cast<int>(d.exponent));
|
||||
w += d.negative ? 1 : 0;
|
||||
// (without leading zeros, all digits of the token count)
|
||||
const unsigned char lead = first[d.negative ? 1 : 0];
|
||||
return lead != '0' ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(int_digits + frac_digits), static_cast<int>(d.exponent))
|
||||
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
|
||||
}
|
||||
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
|
||||
}
|
||||
@@ -6166,13 +6105,13 @@ class view_serializer
|
||||
/// a double as dump() writes it, at w (64 bytes of room)
|
||||
static char* write_double_value_at(char* w, double x)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!std::isfinite(x)))
|
||||
// (from the bits: without the checks of to_chars())
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &x, sizeof(bits));
|
||||
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||
{
|
||||
return write_text_at(w, "null", 4);
|
||||
}
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
std::uint64_t bits = 0;
|
||||
std::memcpy(&bits, &x, sizeof(bits));
|
||||
*w = '-';
|
||||
w += bits >> 63u;
|
||||
bits &= ~(std::uint64_t{1} << 63u);
|
||||
@@ -6180,11 +6119,7 @@ class view_serializer
|
||||
{
|
||||
return write_text_at(w, "0.0", 3);
|
||||
}
|
||||
const ::nlohmann::detail::zmij::decimal d = ::nlohmann::detail::zmij::to_decimal(bits);
|
||||
return write_decimal(w, d.significand, d.exponent);
|
||||
#else
|
||||
return ::nlohmann::detail::to_chars(w, w + 64, x);
|
||||
#endif
|
||||
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
|
||||
}
|
||||
|
||||
/// as serializer::dump_float()
|
||||
|
||||
Reference in New Issue
Block a user