Change LittleEndian loads/stores to use memcpy
The existing code uses a series of 8bit loads with shifts and ors to emulate an (unaligned) load of a larger type. These are then expected to become single loads in the compiler, producing optimal assembly. Whilst this is true it happens very late in the compiler, meaning that throughout most of the pipeline it is treated (and cost-modelled) as multiple loads, shifts and ors. This can make the compiler make poor decisions (such as not unrolling loops that should be), or to break up the pattern before it is turned into a single load. For example the loops in CompressFragment do not get unrolled as expected due to a higher cost than the unroll threshold in clang. Instead this patch uses a more conventional methods of loading unaligned data, using a memcpy directly which the compiler will be able to deal with much more straight forwardly, modelling it as a single unaligned load. The old code is left as-is for big-endian systems. This helps improve the performance of the BM_ZFlat benchmarks by up to 10-15% on an Arm Neoverse N1. Change-Id: I986f845ebd0a0806d052d2be3e4dbcbee91713d7
This commit is contained in:
@@ -171,27 +171,37 @@ class LittleEndian {
|
|||||||
public:
|
public:
|
||||||
// Functions to do unaligned loads and stores in little-endian order.
|
// Functions to do unaligned loads and stores in little-endian order.
|
||||||
static inline uint16_t Load16(const void *ptr) {
|
static inline uint16_t Load16(const void *ptr) {
|
||||||
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
|
||||||
|
|
||||||
// Compiles to a single mov/str on recent clang and gcc.
|
// Compiles to a single mov/str on recent clang and gcc.
|
||||||
|
#if SNAPPY_IS_BIG_ENDIAN
|
||||||
|
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
||||||
return (static_cast<uint16_t>(buffer[0])) |
|
return (static_cast<uint16_t>(buffer[0])) |
|
||||||
(static_cast<uint16_t>(buffer[1]) << 8);
|
(static_cast<uint16_t>(buffer[1]) << 8);
|
||||||
|
#else
|
||||||
|
uint16_t x;
|
||||||
|
memcpy(&x, ptr, 2);
|
||||||
|
return x;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint32_t Load32(const void *ptr) {
|
static inline uint32_t Load32(const void *ptr) {
|
||||||
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
|
||||||
|
|
||||||
// Compiles to a single mov/str on recent clang and gcc.
|
// Compiles to a single mov/str on recent clang and gcc.
|
||||||
|
#if SNAPPY_IS_BIG_ENDIAN
|
||||||
|
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
||||||
return (static_cast<uint32_t>(buffer[0])) |
|
return (static_cast<uint32_t>(buffer[0])) |
|
||||||
(static_cast<uint32_t>(buffer[1]) << 8) |
|
(static_cast<uint32_t>(buffer[1]) << 8) |
|
||||||
(static_cast<uint32_t>(buffer[2]) << 16) |
|
(static_cast<uint32_t>(buffer[2]) << 16) |
|
||||||
(static_cast<uint32_t>(buffer[3]) << 24);
|
(static_cast<uint32_t>(buffer[3]) << 24);
|
||||||
|
#else
|
||||||
|
uint32_t x;
|
||||||
|
memcpy(&x, ptr, 4);
|
||||||
|
return x;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint64_t Load64(const void *ptr) {
|
static inline uint64_t Load64(const void *ptr) {
|
||||||
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
|
||||||
|
|
||||||
// Compiles to a single mov/str on recent clang and gcc.
|
// Compiles to a single mov/str on recent clang and gcc.
|
||||||
|
#if SNAPPY_IS_BIG_ENDIAN
|
||||||
|
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
||||||
return (static_cast<uint64_t>(buffer[0])) |
|
return (static_cast<uint64_t>(buffer[0])) |
|
||||||
(static_cast<uint64_t>(buffer[1]) << 8) |
|
(static_cast<uint64_t>(buffer[1]) << 8) |
|
||||||
(static_cast<uint64_t>(buffer[2]) << 16) |
|
(static_cast<uint64_t>(buffer[2]) << 16) |
|
||||||
@@ -200,30 +210,41 @@ class LittleEndian {
|
|||||||
(static_cast<uint64_t>(buffer[5]) << 40) |
|
(static_cast<uint64_t>(buffer[5]) << 40) |
|
||||||
(static_cast<uint64_t>(buffer[6]) << 48) |
|
(static_cast<uint64_t>(buffer[6]) << 48) |
|
||||||
(static_cast<uint64_t>(buffer[7]) << 56);
|
(static_cast<uint64_t>(buffer[7]) << 56);
|
||||||
|
#else
|
||||||
|
uint64_t x;
|
||||||
|
memcpy(&x, ptr, 8);
|
||||||
|
return x;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void Store16(void *dst, uint16_t value) {
|
static inline void Store16(void *dst, uint16_t value) {
|
||||||
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
|
||||||
|
|
||||||
// Compiles to a single mov/str on recent clang and gcc.
|
// Compiles to a single mov/str on recent clang and gcc.
|
||||||
|
#if SNAPPY_IS_BIG_ENDIAN
|
||||||
|
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
||||||
buffer[0] = static_cast<uint8_t>(value);
|
buffer[0] = static_cast<uint8_t>(value);
|
||||||
buffer[1] = static_cast<uint8_t>(value >> 8);
|
buffer[1] = static_cast<uint8_t>(value >> 8);
|
||||||
|
#else
|
||||||
|
memcpy(dst, &value, 2);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Store32(void *dst, uint32_t value) {
|
static void Store32(void *dst, uint32_t value) {
|
||||||
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
|
||||||
|
|
||||||
// Compiles to a single mov/str on recent clang and gcc.
|
// Compiles to a single mov/str on recent clang and gcc.
|
||||||
|
#if SNAPPY_IS_BIG_ENDIAN
|
||||||
|
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
||||||
buffer[0] = static_cast<uint8_t>(value);
|
buffer[0] = static_cast<uint8_t>(value);
|
||||||
buffer[1] = static_cast<uint8_t>(value >> 8);
|
buffer[1] = static_cast<uint8_t>(value >> 8);
|
||||||
buffer[2] = static_cast<uint8_t>(value >> 16);
|
buffer[2] = static_cast<uint8_t>(value >> 16);
|
||||||
buffer[3] = static_cast<uint8_t>(value >> 24);
|
buffer[3] = static_cast<uint8_t>(value >> 24);
|
||||||
|
#else
|
||||||
|
memcpy(dst, &value, 4);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Store64(void* dst, uint64_t value) {
|
static void Store64(void* dst, uint64_t value) {
|
||||||
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
|
||||||
|
|
||||||
// Compiles to a single mov/str on recent clang and gcc.
|
// Compiles to a single mov/str on recent clang and gcc.
|
||||||
|
#if SNAPPY_IS_BIG_ENDIAN
|
||||||
|
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
||||||
buffer[0] = static_cast<uint8_t>(value);
|
buffer[0] = static_cast<uint8_t>(value);
|
||||||
buffer[1] = static_cast<uint8_t>(value >> 8);
|
buffer[1] = static_cast<uint8_t>(value >> 8);
|
||||||
buffer[2] = static_cast<uint8_t>(value >> 16);
|
buffer[2] = static_cast<uint8_t>(value >> 16);
|
||||||
@@ -232,6 +253,9 @@ class LittleEndian {
|
|||||||
buffer[5] = static_cast<uint8_t>(value >> 40);
|
buffer[5] = static_cast<uint8_t>(value >> 40);
|
||||||
buffer[6] = static_cast<uint8_t>(value >> 48);
|
buffer[6] = static_cast<uint8_t>(value >> 48);
|
||||||
buffer[7] = static_cast<uint8_t>(value >> 56);
|
buffer[7] = static_cast<uint8_t>(value >> 56);
|
||||||
|
#else
|
||||||
|
memcpy(dst, &value, 8);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline constexpr bool IsLittleEndian() {
|
static inline constexpr bool IsLittleEndian() {
|
||||||
|
|||||||
Reference in New Issue
Block a user