Skip to content

Commit 2859042

Browse files
committed
RISC-V: Optimize Snappy decompression tag advance (AdvanceToNextTagRVOptimized) for +4% throughput
1 parent 6281a07 commit 2859042

1 file changed

Lines changed: 31 additions & 0 deletions

File tree

snappy.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,32 @@ inline size_t AdvanceToNextTagX86Optimized(const uint8_t** ip_p, size_t* tag) {
13631363
return tag_type;
13641364
}
13651365

1366+
SNAPPY_ATTRIBUTE_ALWAYS_INLINE
1367+
inline size_t AdvanceToNextTagRVOptimized(const uint8_t** ip_p, size_t* tag) {
1368+
const uint8_t*& ip = *ip_p;
1369+
// This section is crucial for the throughput of the decompression loop.
1370+
// The latency of an iteration is fundamentally constrained by the data chain on ip:
1371+
// ip -> c = *tag -> literal_len = c >> 2, tag_type = c & 3
1372+
// -> literal_advance = literal_len + 2, copy_advance = tag_type + 1
1373+
// -> next_ip = ip + literal_advance OR ip + copy_advance (literal vs copy)
1374+
// -> *tag = byte at (next_ip - 1); ip = next_ip
1375+
//
1376+
// Base RISC-V has no x86-style cmov and no AArch64 csinc on the same shape; this
1377+
// computes both candidate advances and both load offsets, then selects with
1378+
// (is_literal ? ... : ...). With the Zicond extension (czero.eqz / czero.nez), those
1379+
// selections typically lower to branchless conditional-zero ops instead of a
1380+
// hard-to-predict literal/copy branch, which is why this form tends to win there.
1381+
const size_t literal_len = *tag >> 2;
1382+
const size_t tag_type = *tag & 3;
1383+
const bool is_literal = (tag_type == 0);
1384+
const size_t copy_advance = tag_type + 1;
1385+
const size_t literal_advance = literal_len + 2;
1386+
const uint8_t* next_ip = is_literal ? (ip + literal_advance) : (ip + copy_advance);
1387+
*tag = is_literal ? ip[literal_advance - 1] : ip[copy_advance - 1];
1388+
ip = next_ip;
1389+
return tag_type;
1390+
}
1391+
13661392
// Extract the offset for copy-1 and copy-2 returns 0 for literals or copy-4.
13671393
inline uint32_t ExtractOffset(uint32_t val, size_t tag_type) {
13681394
// For x86 non-static storage works better. For ARM static storage is better.
@@ -1439,6 +1465,11 @@ std::pair<const uint8_t*, ptrdiff_t> DecompressBranchless(
14391465
// We never need more than 16 bits. Doing a Load16 allows the compiler
14401466
// to elide the masking operation in ExtractOffset.
14411467
next = LittleEndian::Load16(old_ip);
1468+
#elif defined(__riscv)
1469+
size_t tag_type = AdvanceToNextTagRVOptimized(&ip, &tag);
1470+
// We never need more than 16 bits. Doing a Load16 allows the compiler
1471+
// to elide the masking operation in ExtractOffset.
1472+
next = LittleEndian::Load16(old_ip);
14421473
#else
14431474
size_t tag_type = AdvanceToNextTagX86Optimized(&ip, &tag);
14441475
next = LittleEndian::Load32(old_ip);

0 commit comments

Comments
 (0)