SIMD optimization patterns and learnings for x86_64 and ARM. Use when implementing SIMD code, optimizing vectorized operations, or debugging SIMD issues...
Patterns and learnings from SIMD optimization in this codebase.
Comprehensive documentation: See docs/optimizations/simd.md for full details on SIMD techniques.
For anything cache- or bandwidth-bound, the effect size differs by architecture, not just the noise โ so a single-platform measurement can mischaracterise a change, not merely blur it.
O6 (#106) removed a repeated interest-bitmap rescan. The same commit measured 6.1x on Apple
M4 Pro and 16.4x on Ryzen 9 7950X. Post-fix times were comparable (124 ms vs 137 ms); the
pre-fix times differed 3x (758 ms vs 2240 ms), because Apple's memory subsystem absorbed the
thrashing far better than Zen 4. Measuring only Apple Silicon understated the fix by 2.7x. The
same asymmetry is why project_benchmark_feature_flags-style
AVX-512 findings must never be presented as universal.
Rule: any claim about a memory-bound path needs both an ARM and an x86_64 number, and the tables must name the chip. See docs/guides/benchmarking.md ยง A/B Benchmarking Method.
Two AVX-512 optimizations implemented with dramatically different results:
count_ones(), โ1ร Native (Compute-Bound)Implementation: src/bits/popcount.rs
_mm512_popcnt_epi64 instructioncount_ones() (which lowers to scalar broadword) โ e.g. 96.8 GiB/s vs 18.5 GiB/s.Why it wins โ but only vs a baseline build: Pure compute-bound and embarrassingly parallel, so explicit VPOPCNTDQ crushes scalar broadword. But compile with -C target-cpu=native and count_ones() auto-vectorizes to VPOPCNTDQ itself, reaching โ1ร parity โ the explicit path's remaining value is portable binaries that still reach VPOPCNTDQ via runtime is_x86_feature_detected! dispatch. Measured data: Popcount Strategies (#45).
Why AVX2 won:
| Level | Width | Bytes/Iter | Availability | Notes |
|---|---|---|---|---|
| SSE2 | 128bit | 16 | 100% | Universal baseline on x86_64 |
| SSE4.2 | 128bit | 16 | ~90% | PCMPISTRI string instructions |
| AVX2 | 256bit | 32 | ~95% | 2x width, best price/performance |
| BMI2 | N/A | N/A | ~95% | PDEP/PEXT, but AMD Zen 1/2 slow |
Key insight: #[target_feature] is a compiler directive, not a runtime gate.
// All these compile on any x86_64:
#[target_feature(enable = "sse2")]
unsafe fn process_sse2(data: &[u8]) { ... }
#[target_feature(enable = "avx2")]
unsafe fn process_avx2(data: &[u8]) { ... }
// Runtime dispatch (requires std)
fn process(data: &[u8]) {
if is_x86_feature_detected!("avx2") {
unsafe { process_avx2(data) }
} else {
unsafe { process_sse2(data) }
}
}
Problem: NEON lacks x86's _mm_movemask_epi8. Variable shifts are slow on M1.
Solution: Multiplication trick to pack bits:
#[inline]
#[target_feature(enable = "neon")]
unsafe fn neon_movemask(v: uint8x16_t) -> u16 {
let high_bits = vshrq_n_u8::<7>(v);
let low_u64 = vgetq_lane_u64::<0>(vreinterpretq_u64_u8(high_bits));
let high_u64 = vgetq_lane_u64::<1>(vreinterpretq_u64_u8(high_bits));
const MAGIC: u64 = 0x0102040810204080;
let low_packed = (low_u64.wrapping_mul(MAGIC) >> 56) as u8;
let high_packed = (high_u64.wrapping_mul(MAGIC) >> 56) as u8;
(low_packed as u16) | ((high_packed as u16) << 8)
}
Results: 10-18% improvement on string-heavy and nested JSON patterns.
SSE2 lacks unsigned byte comparison. Use min trick:
unsafe fn unsigned_le(a: __m128i, b: __m128i) -> __m128i {
let min_ab = _mm_min_epu8(a, b);
_mm_cmpeq_epi8(min_ab, a) // a <= b iff min(a,b) == a
}
#[inline(always)] + #[target_feature] Won't CompileA #[target_feature] function may not also be #[inline(always)] โ modern
rustc rejects the combination outright (rust#145574). Put #[inline] on the
SIMD kernel (the compiler still inlines it under -O), and reserve
#[inline(always)] for the non-target_feature dispatch wrapper. This split is
load-bearing for escape scanning: O3 (#87) showed #[inline(always)] on the
public entry is required to avoid a 3-5% regression, so the wrapper is
#[inline(always)] while the per-backend mask kernels are #[inline].
src/util/simd/escape.rs factors the 16/32-byte chunk loop + scalar remainder
behind a define_escape_scanner! macro parameterized by the escape predicate, so
a new scanner (@html/@uri/@csv; #124) is a macro invocation rather than a
copy of the SIMD machinery. find_json_escape is the only instantiation today;
it is re-exported from yaml::simd for compatibility. The scalar predicate and
three per-backend mask helpers (NEON/AVX2/SSE2) are the only predicate-specific
code โ verify each new predicate with an exhaustive 256-byte ร offset parity test
against its scalar reference (the test that caught the signed-compare bug #230).
lo_table[byte & 0xF] & hi_table[byte >> 4] classifies each bit plane as the
Cartesian product {lo nibbles} ร {hi nibbles} it is set for. A byte set is
encoded exactly only as a union of such products, one bit plane per
product:
A-Z (0x41-0x5A) spans two hi nibbles โ needs two planes
({1..F}ร{4} and {0..A}ร{5}). One shared "uppercase" plane matches all
of 0x40-0x5F, over-matching @ \ ^ _.{',' ':'} is not a product ({A,C} ร {2,3} also contains * and <) โ
needs a plane per character.Over-matches hide on valid input (the extra bytes are invalid JSON there) and
surface as cross-backend index divergence on fuzzed/malformed input. Verify
tables with an exhaustive 256-byte test against the scalar predicate (see
json/simd/neon.rs table tests).
Problem: Runtime dispatch only tests highest available SIMD level.
Solution: Explicitly call each implementation in tests:
#[test]
fn test_all_simd_levels() {
let input = b"test input";
let expected = scalar_impl(input);
let sse2_result = unsafe { sse2::process(input) };
let avx2_result = unsafe { avx2::process(input) };
assert_eq!(sse2_result, expected);
assert_eq!(avx2_result, expected);
}
is_x86_feature_detected! requires stdAlternatives:
count_ones() - LLVM optimizes to POPCNT with -C target-feature=+popcnt#[target_feature(enable = "popcnt")] on functions#[cfg(test)] blocksNo runtime detection needed:
#[cfg(target_arch = "aarch64")]
{
// NEON intrinsics work without feature detection
unsafe { neon::process(data) }
}
# Test AVX-512 popcount implementation
cargo test --lib --features simd popcount
# Benchmark popcount strategies
cargo bench --bench popcount_strategies --features simd
# Run comprehensive JSON benchmarks
cargo bench --bench json_simd