Add swizzle_dyn_precise, mirroring swizzle_dyn from std::simd - #276
Add swizzle_dyn_precise, mirroring swizzle_dyn from std::simd#276Shnatsel wants to merge 23 commits into
std::simd#276Conversation
|
I've also submitted similar changes upstream to
One PR is already merged, the other is awaiting review. |
|
The implementation has evolved somewhat since the PR description was written, here's a blog post that goes into detail and is more representative of the current state: https://shnatsel.github.io/improving-std-simd-swizzle-dyn/ |
…on to see what happens" This reverts commit de4a2c7.
…ecise and we can assert it has to always match the scalar fallback
… it's precise and we can assert it has to always match the scalar fallback" to avoid adding extra dependencies This reverts commit 4e8a56a.
…eric split/combine since there's no difference on benchmarks on real Zen2 hardware, which was the best-case scenario for this implementation according to llvm-mca
… llvm-mca results: Haswell, Broadwell and Skylake effectively unchanged for the 256-bit case; Zen1, Tiger Lake show 23% improvement, Zen3 7% to 20% improvement. The 512-bit case that decomposes into this is about the same, but with a 20% improvement on Skylake and a clear 20% improvement on Zen3.
0a551a7 to
8aee113
Compare
# Conflicts: # fearless_simd_tests/tests/harness/lm_generated.rs
…e from vpcmpgtb + vpblendvb to vpaddb + vpor
|
anxsan12 suggested a further optimization that turned out to be worthwhile, so I applied it. It is also merged upstream: rust-lang/portable-simd#543 |
Doubles the throughput on both Ice Lake and Zen4. Improves latency on Ice Lake from 23 to 13 cycles, on Zen4 from 17 to 15 cycles. Improves encoding performance of a toy base64 implementation on Zen4 by -20% (time) +25% (throughput), no change on decoding benchmark
|
@DJMcNab could you take a quick look? All the complex parts (x86 implementations, 2x vector width trick) have already been reviewed upstream in
WASM and NEON implementations are trivial. There are unit tests and random testing to ensure correctness, so I'm quite confident in this code. |
I wondered how
std::simdlowersswizzle_dyn- not the limited within_blocks version we added in #266 but the full arbitrary version - and the answer turned out to be "poorly". So I got nerd-sniped, badly. It's 4:30 AM as I write this.The in-tree std::simd is trivially suboptimal, not even taking the most obvious optimization avenues like using 512-bit table lookups on NEON. The NEON gap is fixed upstream but not synced into std yet.
They also lower into a slow, branchy scalar fallback for all cases that don't have a native shuffle op available. There is a better way: if all you have is 128-bit vectors, you can get a 256-bit vector shuffle with four 128-bit shuffles and a couple bitwise ORs.
This PR fixes all of that:
Adds a further optimized 512-bit shuffle on AVX2 which is better than the generic decompositionI've written a toy SIMD base64 decoder using 512-bit shuffles to benchmark this. On Zen4 the specialized AVX2 implementation improves the benchmark by 5%, but it'll never use it because it has AVX-512. On Zen2, where the specialized 512-bit AVX2 is supposed to help the most, there was no difference on benchmarks on real hardware at all, so I went ahead and deleted the specialized 512-bit AVX2 implementation.
Hand-written unit tests are included. Tests on random data that check the optimized SIMD impls against a trivial scalar impl were used to verify the implementation but aren't included in the final PR to avoid extra deps and slow tests (they're preserved in history for posterity but reverted).
I've implemented the *_precise variant first because it is genuinely important for some algorithms, mirrors the std::simd implementation exactly, and the "out of bounds is zero" property actually allows for cool optimizations for non-native vector widths.
This probably deserves a blog post.