diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bdcb05..0b8a070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable WIMF changes are recorded here. The project follows semantic version ## 2.2.0 - Unreleased +- Upgraded the scalar CRC-32 to slice-by-8: all eight slice tables are derived + from the polynomial at compile time, and the bulk loop consumes eight bytes + per iteration instead of one (typically 4-6x table throughput on every + platform, including x86 where no IEEE hardware CRC instruction exists). - First slice of the context-modeled entropy stage (known-flaws A3): lossy wavelet tiles now use marker-free (run, zigzag) coefficient tokens flagged by the tile's reversible byte value 2, removing one mandatory byte per token diff --git a/src/v2_simd.cpp b/src/v2_simd.cpp index 542ca08..63b70b8 100644 --- a/src/v2_simd.cpp +++ b/src/v2_simd.cpp @@ -6,6 +6,8 @@ #include "v2_simd.hpp" +#include + #if defined(WIMF_AVX2_KERNELS) && defined(_MSC_VER) && !defined(__clang__) #include #include @@ -31,6 +33,24 @@ struct CrcTable { }; constexpr CrcTable kCrcTable{}; +// Slice-by-8 tables: entry [k][i] is the CRC after consuming byte i followed +// by k zero bytes, derived entirely from the polynomial at compile time. +// Processing eight bytes per iteration removes most of the serial dependency +// chain of the classic one-byte table loop (typically 4-6x throughput). +struct CrcSlices { + uint32_t entries[8][256]; + constexpr CrcSlices() : entries{} { + for (uint32_t i = 0; i < 256; ++i) { + entries[0][i] = crc_entry(i); + for (int k = 1; k < 8; ++k) { + const uint32_t previous = entries[k - 1][i]; + entries[k][i] = (previous >> 8) ^ crc_entry(previous & 0xFFu); + } + } + } +}; +constexpr CrcSlices kCrcSlices{}; + struct Features { bool avx2 = false; bool hardware_crc32 = false; @@ -83,6 +103,21 @@ bool has_hardware_crc32() noexcept { return features().hardware_crc32; } uint32_t crc32_table(const uint8_t* data, size_t size) noexcept { uint32_t crc = 0xFFFFFFFFu; + // Slice-by-8 main loop: fold eight bytes per iteration through the + // precomputed slice tables. Byte order matches the little-endian load of + // the first four bytes into the running CRC. + while (size >= 8) { + uint32_t low, high; + std::memcpy(&low, data, sizeof(low)); + std::memcpy(&high, data + 4, sizeof(high)); + low ^= crc; + crc = kCrcSlices.entries[7][low & 0xFFu] ^ kCrcSlices.entries[6][(low >> 8) & 0xFFu] ^ + kCrcSlices.entries[5][(low >> 16) & 0xFFu] ^ kCrcSlices.entries[4][low >> 24] ^ + kCrcSlices.entries[3][high & 0xFFu] ^ kCrcSlices.entries[2][(high >> 8) & 0xFFu] ^ + kCrcSlices.entries[1][(high >> 16) & 0xFFu] ^ kCrcSlices.entries[0][high >> 24]; + data += 8; + size -= 8; + } for (size_t i = 0; i < size; ++i) crc = (crc >> 8) ^ kCrcTable.entries[(crc ^ data[i]) & 0xFFu]; return ~crc;