Encode big-endian numbers with a byte swap instead of std::reverse

write_number() reordered multi-byte numbers for the big-endian formats
(CBOR/MessagePack/UBJSON) with std::reverse over the byte array. GCC
lowered only some sizes to a bswap; clang kept a scalar byte shuffle
(0 bswap instructions in the CBOR number path). Replace the reverse with
size-dispatched __builtin_bswap16/32/64 helpers (portable shift fallback
for other compilers; std::reverse retained for exotic sizes such as a
long double number_float_t).

Codegen: the CBOR number path now emits bswap on both compilers
(gcc 2 -> 16, clang 0 -> 4). Output is byte-for-byte identical to the
previous implementation across the binary differential corpus.

Throughput (isolated vs the std::reverse version, best of 9):
  CBOR int64 array   gcc +7%   clang +10%
  CBOR uint16 array  gcc +27%  clang flat

Modest but consistent on number-dense encodings; negligible on
string/blob-heavy output, as expected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAYM1qhSA2FDaDcGfPW3fG
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-08-19 20:16:24 +02:00
co-authored by Claude Opus 4.8
parent 3050e8360d
commit fc8159cc3a
2 changed files with 132 additions and 2 deletions
@@ -1825,6 +1825,71 @@ class binary_writer
On the other hand, BSON and BJData use little endian and should reorder On the other hand, BSON and BJData use little endian and should reorder
on big endian systems. on big endian systems.
*/ */
// single-instruction byte swaps (compilers lower these to bswap/rev/movbe);
// used to emit big-endian numbers without a per-byte std::reverse loop
static std::uint16_t byte_swap(std::uint16_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap16(x);
#else
return static_cast<std::uint16_t>((x >> 8) | (x << 8));
#endif
}
static std::uint32_t byte_swap(std::uint32_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap32(x);
#else
return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8)
| ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24);
#endif
}
static std::uint64_t byte_swap(std::uint64_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(x);
#else
x = ((x & 0x00000000FFFFFFFFull) << 32) | ((x & 0xFFFFFFFF00000000ull) >> 32);
x = ((x & 0x0000FFFF0000FFFFull) << 16) | ((x & 0xFFFF0000FFFF0000ull) >> 16);
x = ((x & 0x00FF00FF00FF00FFull) << 8) | ((x & 0xFF00FF00FF00FF00ull) >> 8);
return x;
#endif
}
// reverse the bytes of a fixed-size buffer; a single byte_swap() for the
// common 2/4/8-byte number payloads, std::reverse for any other size
static void reverse_bytes(std::array<CharType, 2>& a) noexcept
{
std::uint16_t v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
static void reverse_bytes(std::array<CharType, 4>& a) noexcept
{
std::uint32_t v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
static void reverse_bytes(std::array<CharType, 8>& a) noexcept
{
std::uint64_t v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
template<std::size_t N>
static void reverse_bytes(std::array<CharType, N>& a) noexcept
{
std::reverse(a.begin(), a.end());
}
template<typename NumberType> template<typename NumberType>
void write_number(const NumberType n, const bool OutputIsLittleEndian = false) void write_number(const NumberType n, const bool OutputIsLittleEndian = false)
{ {
@@ -1836,7 +1901,7 @@ class binary_writer
if (is_little_endian != OutputIsLittleEndian) if (is_little_endian != OutputIsLittleEndian)
{ {
// reverse byte order prior to conversion if necessary // reverse byte order prior to conversion if necessary
std::reverse(vec.begin(), vec.end()); reverse_bytes(vec);
} }
oa.write_characters(vec.data(), sizeof(NumberType)); oa.write_characters(vec.data(), sizeof(NumberType));
+66 -1
View File
@@ -18582,6 +18582,71 @@ class binary_writer
On the other hand, BSON and BJData use little endian and should reorder On the other hand, BSON and BJData use little endian and should reorder
on big endian systems. on big endian systems.
*/ */
// single-instruction byte swaps (compilers lower these to bswap/rev/movbe);
// used to emit big-endian numbers without a per-byte std::reverse loop
static std::uint16_t byte_swap(std::uint16_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap16(x);
#else
return static_cast<std::uint16_t>((x >> 8) | (x << 8));
#endif
}
static std::uint32_t byte_swap(std::uint32_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap32(x);
#else
return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8)
| ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24);
#endif
}
static std::uint64_t byte_swap(std::uint64_t x) noexcept
{
#if defined(__GNUC__) || defined(__clang__)
return __builtin_bswap64(x);
#else
x = ((x & 0x00000000FFFFFFFFull) << 32) | ((x & 0xFFFFFFFF00000000ull) >> 32);
x = ((x & 0x0000FFFF0000FFFFull) << 16) | ((x & 0xFFFF0000FFFF0000ull) >> 16);
x = ((x & 0x00FF00FF00FF00FFull) << 8) | ((x & 0xFF00FF00FF00FF00ull) >> 8);
return x;
#endif
}
// reverse the bytes of a fixed-size buffer; a single byte_swap() for the
// common 2/4/8-byte number payloads, std::reverse for any other size
static void reverse_bytes(std::array<CharType, 2>& a) noexcept
{
std::uint16_t v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
static void reverse_bytes(std::array<CharType, 4>& a) noexcept
{
std::uint32_t v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
static void reverse_bytes(std::array<CharType, 8>& a) noexcept
{
std::uint64_t v{};
std::memcpy(&v, a.data(), sizeof(v));
v = byte_swap(v);
std::memcpy(a.data(), &v, sizeof(v));
}
template<std::size_t N>
static void reverse_bytes(std::array<CharType, N>& a) noexcept
{
std::reverse(a.begin(), a.end());
}
template<typename NumberType> template<typename NumberType>
void write_number(const NumberType n, const bool OutputIsLittleEndian = false) void write_number(const NumberType n, const bool OutputIsLittleEndian = false)
{ {
@@ -18593,7 +18658,7 @@ class binary_writer
if (is_little_endian != OutputIsLittleEndian) if (is_little_endian != OutputIsLittleEndian)
{ {
// reverse byte order prior to conversion if necessary // reverse byte order prior to conversion if necessary
std::reverse(vec.begin(), vec.end()); reverse_bytes(vec);
} }
oa.write_characters(vec.data(), sizeof(NumberType)); oa.write_characters(vec.data(), sizeof(NumberType));