rdeflate: CRC hash key read with one load, not three

Every position the encoder inserts into a hash chain - on screenshot
content, nearly every position - first builds the 24-bit key for the
CRC32 hash from three byte loads, two shifts and two ors.  That was
the single most expensive line in the encoder: 522 million
instructions for three 4K captures, on smooth and noisy images alike.

On little-endian targets the key is now one 32-bit load with the top
byte masked off, which is the same value.  The fourth byte it reads
is always inside win[]: a key is only taken where three bytes remain
before win_len, and win[] already carries an 8-byte margin past its
largest win_len for the word compares in rd_longest_match().
Big-endian targets keep the byte-wise form, and builds without a CRC
hash (shift-xor only) are untouched.

Compressed output is byte-identical to before over the level 0-9 x
input chunking x six-input matrix, zlib-wrapped and raw, for each
hash build: runtime-detected SSE4.2, -msse4.2, and aarch64 +crc
under qemu; valgrind memcheck clean over the same matrix.  rzip,
rpng, archive and encodings sample tests pass; builds C89 without
new warnings, for aarch64 with and without +crc, armv7, and
big-endian PowerPC.

screenshot_io_bench, 3840x2160 BGR24 PNG: gradient instructions
-11.3% (3.29 G -> 2.92 G), median about -10%; noise -4.0%, best
368 -> 351 ms.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-07 19:32:20 +01:00
1 parent 7b44ddac58
commit 0ca986d2d4
1 file changed
+25 -4
+25 -4
View File
@@ -2455,13 +2455,34 @@ static void rd_fixed_lit_lengths(uint8_t *ll)
# endif
#endif
#if defined(RD_CRC32_HASH_DIRECT) || defined(RD_CRC32_HASH_RUNTIME)
/* The three bytes at p as the CRC hash key, p[0] lowest. On a
* little-endian target that is one 32-bit load with the top byte
* dropped, in place of three byte loads, two shifts and two ors - the
* key is built at every position inserted, which on the content
* screenshots hold is nearly every position. The fourth byte read
* always lies inside win[]: a key is only taken where three bytes
* remain before win_len, and win[] carries an 8-byte margin past its
* largest win_len for the word compares in rd_longest_match(). */
static INLINE uint32_t rd_key3(const uint8_t *p)
{
#if RETRO_IS_BIG_ENDIAN
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
#else
uint32_t k;
memcpy(&k, p, 4);
return k & 0x00FFFFFFu;
#endif
}
#endif
#if defined(RD_CRC32_HASH_RUNTIME)
#include <features/features_cpu.h>
#if defined(_MSC_VER)
#include <nmmintrin.h>
static uint32_t rd_hash_crc(const uint8_t *p)
{
uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
uint32_t k = rd_key3(p);
return _mm_crc32_u32(0, k) & RD_HASH_MASK;
}
#else
@@ -2473,7 +2494,7 @@ static uint32_t rd_hash_crc(const uint8_t *p)
* selects this path when cpu_features_get() reports SSE4.2. */
static uint32_t rd_hash_crc(const uint8_t *p)
{
uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
uint32_t k = rd_key3(p);
uint32_t r = 0;
__asm__("crc32l %1, %0" : "+r"(r) : "rm"(k));
return r & RD_HASH_MASK;
@@ -2493,11 +2514,11 @@ static uint32_t rd_hash_soft(const uint8_t *p)
static uint32_t rd_hash(struct rdeflate *s, const uint8_t *p)
{
#if defined(__SSE4_2__)
uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
uint32_t k = rd_key3(p);
(void)s;
return _mm_crc32_u32(0, k) & RD_HASH_MASK;
#elif defined(__ARM_FEATURE_CRC32)
uint32_t k = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16);
uint32_t k = rd_key3(p);
(void)s;
return __crc32w(0, k) & RD_HASH_MASK;
#elif defined(RD_CRC32_HASH_RUNTIME)