我找到了一个更简单且不需要的SIMD解决方案 signed*unsigned 产品。 我不再相信SIMD(至少与AVX2和AV512一样)无法与之竞争 mulx 。 在某些情况下,SIMD可以与之竞争 mulx 。我所知道的唯一案例是 基于FFT的大数乘法 。
signed*unsigned
mulx
诀窍是先做无符号乘法,然后再纠正。我从这个答案中学到了如何做到这一点 32位签名-使用-64位数据类型乘法而不- 。修正很简单 (hi,lo) = x*y 先做无符号乘法,然后再纠正 hi 像这样:
(hi,lo) = x*y
hi
hi -= ((x<0) ? y : 0) + ((y<0) ? x : 0)
这可以通过SSE4.2内在来完成 _mm_cmpgt_epi64
_mm_cmpgt_epi64
void muldws1_sse(__m128i x, __m128i y, __m128i *lo, __m128i *hi) { muldwu1_sse(x,y,lo,hi); //hi -= ((x<0) ? y : 0) + ((y<0) ? x : 0); __m128i xs = _mm_cmpgt_epi64(_mm_setzero_si128(), x); __m128i ys = _mm_cmpgt_epi64(_mm_setzero_si128(), y); __m128i t1 = _mm_and_si128(y,xs); __m128i t2 = _mm_and_si128(x,ys); *hi = _mm_sub_epi64(*hi,t1); *hi = _mm_sub_epi64(*hi,t2); }
无符号乘法的代码更简单,因为它不需要混合 signed*unsigned 产品。另外,因为它是无符号的,所以它不需要算术右移,它只有AVX512的指令。实际上以下功能只需要SSE2:
void muldwu1_sse(__m128i x, __m128i y, __m128i *lo, __m128i *hi) { __m128i lomask = _mm_set1_epi64x(0xffffffff); __m128i xh = _mm_shuffle_epi32(x, 0xB1); // x0l, x0h, x1l, x1h __m128i yh = _mm_shuffle_epi32(y, 0xB1); // y0l, y0h, y1l, y1h __m128i w0 = _mm_mul_epu32(x, y); // x0l*y0l, x1l*y1l __m128i w1 = _mm_mul_epu32(x, yh); // x0l*y0h, x1l*y1h __m128i w2 = _mm_mul_epu32(xh, y); // x0h*y0l, x1h*y0l __m128i w3 = _mm_mul_epu32(xh, yh); // x0h*y0h, x1h*y1h __m128i w0l = _mm_and_si128(w0, lomask); //(*) __m128i w0h = _mm_srli_epi64(w0, 32); __m128i s1 = _mm_add_epi64(w1, w0h); __m128i s1l = _mm_and_si128(s1, lomask); __m128i s1h = _mm_srli_epi64(s1, 32); __m128i s2 = _mm_add_epi64(w2, s1l); __m128i s2l = _mm_slli_epi64(s2, 32); //(*) __m128i s2h = _mm_srli_epi64(s2, 32); __m128i hi1 = _mm_add_epi64(w3, s1h); hi1 = _mm_add_epi64(hi1, s2h); __m128i lo1 = _mm_add_epi64(w0l, s2l); //(*) //__m128i lo1 = _mm_mullo_epi64(x,y); //alternative *hi = hi1; *lo = lo1; }
这用
4x mul_epu32 5x add_epi64 2x shuffle_epi32 2x and 2x srli_epi64 1x slli_epi64 **************** 16 instructions
AVX512有 _mm_mullo_epi64 内在的,可以计算 lo 一条指令。在这种情况下,可以使用替代方法(使用(*)注释注释行并取消注释替代行):
_mm_mullo_epi64
lo
5x mul_epu32 4x add_epi64 2x shuffle_epi32 1x and 2x srli_epi64 **************** 14 instructions
要更改全宽AVX2代码 _mm 同 _mm256 , si128 同 si256 ,和 __m128i 同 __m256i 用于AVX512替换它们 _mm512 , si512 ,和 __m512i 。
_mm
_mm256
si128
si256
__m128i
__m256i
_mm512
si512
__m512i
使用各种指令考虑整数乘法的吞吐量限制的正确方法是根据每个周期可以计算多少“产品位”。
mulx 产生一个64x64 - &gt;每个周期结果128;这是每周期64x64 = 4096“产品位”
如果你将SIMD上的乘数拼凑成32x32的指令 - &gt; 64位乘法,你需要能够在每个周期得到4个结果才能匹配 mulx (4x32x32 = 4096)。如果除了乘法之外没有算术,你只需要在AVX2上收支平衡。不幸的是,正如你已经注意到的那样,除了乘法之外还有很多算术,所以这是当前一代硬件的非启动性。