3 ms·
> Why would you need to rotate the lanes? To half the count of RAM access instructions. Even L1D cache hit is still slower than these two instructions for shuf
by Const-me 6y ago
> Why would you need to rotate the lanes?
To half the count of RAM access instructions. Even L1D cache hit is still slower than these two instructions for shuffle and insert, both are quite fast, they deliver the result on the next cycle.
> GCC simply does the unaligned load
Right, that’s twice as many loads as necessary.
> Clang does the shuffle successfully
I wouldn’t call that code “successfully”. Clang uses shufps instructions to shuffle integer vectors. On many CPUs, especially older than a couple of years, there’s a non-trivial latency, a few cycles, to pass data between integer and floating-point execution units. The approach is correct with these shuffles, but not the implementation. For integers it should have used at least _mm_shuffle_epi32 = pshufd or _mm_slli_si128 = pslldq, but ideally _mm_alignr_epi8 = palignr from SSSE3.
- dragontamer 6y agoI guess we have different measures of "success". I don't expect any compiler to beat me in intrinsics or hand-crafted assembly. After all, I can use the compiler. But the compiler can't use my brain. So in all circumstances, I will always at least tie the compiler. Its an "auto-vectorization success", because the pseudo-code I've laid out auto-vectorizes to SSE 128-bit, AVX 256-bit, and AVX512 512-bit code. Furthermore, CLang supports many architectures, and probably can convert my simple for-loop into SIMD on other architectures without any intervention (ie: ARM Neon) The code doesn't need to be optimal, it just needs to be faster than the scalar form and portable.
- Const-me 6y agoYes and no. “yes” because what clang did is a very good job, pretty sure it substantially faster than scalar code. Especially on newish CPUs like Zen2. “no“ because from what they have currently for that particular test case, they only need a few improvements to make their code as fast as a manually vectorized version. Apparently, the compiler is already aware of the cost of RAM loads. They just need to account for these bypass delays between integer and FP execution units in their code generator.
- nkurz 6y agoFrequently, when one is using SIMD one is more concerned about throughput (how many operations you can do per second) and less about latency (how many nanoseconds until you get there first result back). I'm not sure if you are equating the two in your comments, or have an actual preference for lower latency, but realize that one can often gain in throughput by giving up some latency. I mention this because I don't share your certainty that avoiding duplicate loads is necessarily "faster". I've occasionally been able beat compilers at getting higher throughput by making the duplicate load, despite the fact the latency increases. This is because vector shuffles (and I think aligns and inserts) are limited to a single execution port (at least on recent Intel processors), while loads can be executed twice per cycle. Depending on the mix of the rest of instructions, this means that sometimes the redundant load ends up being the better choice. Which is to say, rather than being sure, measure!
- Const-me 6y agoI agree in general, and yes, I always use a profiler before writing these intrinsics. But still, that particular clang output is suboptimal. They have already optimized loads with these shuffles. I think they did 80% of the work already. They are just using the wrong shuffles.