You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
BitPacked compare and between are 3x to 7x slower on baseline x86-64 than CodSpeed shows #10396
Following up on the analysis in #8280: Vortex builds for baseline x86-64 (SSE2) by default (the vx release binary, crates.io users without target flags), while CodSpeed builds with +avx2 and the walltime legs use avx2, avx512 and neon. SSE2 has no 64-bit compare, so the BitPacked kernels compare u64 values one at a time there. stream_predicate stays on the inline baseline path on purpose (wider kernels behind #[target_feature] would not inline the predicate), so this gap does not show up in any benchmark.
Measured on develop (2be9821), Zen 5, 131072 u64 values, BitPacked 27 bits, divan median:
baseline x86-64
target-cpu=native
BitPacked Lt against a constant
72.6 µs
13.8 µs
BitPacked between
112 µs
42.1 µs
FoR Eq above the range
45.2 µs
14.5 µs
Proposed solution
BitPacked knows the bit width W, so for W <= 31 every value and the constant fit in i32, and SSE2 does have a 32-bit compare: put the low halves of four u64 values side by side (shuffle_ps), one pcmpgtd, movmskps for four mask bits. These are SSE2 intrinsics, part of the x86-64 baseline, so there is no #[target_feature] boundary and the predicate still inlines.
Microbenchmark over one 1024-value block in L1 (the stream_predicate scratch), u64 values with 27 significant bits, v < c into a bitmap:
baseline x86-64
+avx2
today's shape
0.42 ns/value
0.064 ns/value
i32 lanes (W <= 31)
0.058 ns/value
0.060 ns/value
So 7x on baseline and nothing lost with AVX2. A second, smaller one: between as a single unsigned compare, (v - lo) <= (hi - lo) with wrapping subtraction, is 1.56x on baseline for u64, and applies to primitive between too.
Is baseline x86-64 a target you want to optimize for? If so, I can open a PR for stream_predicate, and a baseline leg in CodSpeed would keep it measured.
Additional context
The 0.058 ns/value is bound by loading the u64 scratch from L1. Going further would mean unpacking straight into u32 or u16 lanes, or comparing inside fastlanes' unpack_cmp, which belongs in the fastlanes crate.
Correction: compare doesn't use stream_predicate but the fused unpack_cmp (#8239), so the Lt fix belongs in fastlanes (see #9699). I have two changes ready. A bit-width shortcut takes FoR Eq above the range from 32 µs to 1.1 µs. Between through the fused kernel is 1.7x faster on the default build, but still 4x behind native.
@joseph-isaacs the shortcut is from your #8013. OK if I open a PR building on it?
Activity
mightsleep commentedon Oct 12, 2026
Correction: compare doesn't use stream_predicate but the fused unpack_cmp (#8239), so the Lt fix belongs in fastlanes (see #9699). I have two changes ready. A bit-width shortcut takes FoR Eq above the range from 32 µs to 1.1 µs. Between through the fused kernel is 1.7x faster on the default build, but still 4x behind native.
@joseph-isaacs the shortcut is from your #8013. OK if I open a PR building on it?