使用 AVX2 实现 _mm256_mullo_epi4 的最快方法

Pin*_*oyd 5 c x86-64 intrinsics avx avx2

对于研究问题,我需要使用 AVX2/AVX 指令实现非常高效的 4 位乘法(仅需要低 4 位)。

我目前的做法是:

__m256i _mm256_mullo_epi4(const __m256i a, const __m256i b) {
    __m256i mask_f_0 = _mm256_set1_epi16(0x000f);
    __m256i tmp_mul_0 = _mm256_and_si256(_mm256_mullo_epi16(a, b), mask_f_0);
    __m256i tmp_mul_1 = _mm256_and_si256(_mm256_mullo_epi16(_mm256_srli_epi16(a,   4), _mm256_srli_epi16(b,   4)), mask_f_0);
    __m256i tmp_mul_2 = _mm256_and_si256(_mm256_mullo_epi16(_mm256_srli_epi16(a,   8), _mm256_srli_epi16(b,   8)), mask_f_0);
    __m256i tmp_mul_3 = _mm256_and_si256(_mm256_mullo_epi16(_mm256_srli_epi16(a,  12), _mm256_srli_epi16(b,  12)), mask_f_0);
    __m256i tmp1 = _mm256_xor_si256(tmp_mul_0, _mm256_slli_epi16(tmp_mul_1, 4));
    __m256i tmp2 = _mm256_xor_si256(tmp1, _mm256_slli_epi16(tmp_mul_2, 8));
    __m256i tmp  = _mm256_xor_si256(tmp2, _mm256_slli_epi16(tmp_mul_3, 12));
    return tmp;
}
Run Code Online (Sandbox Code Playgroud)

此实现利用相对昂贵的_mm256_mullo_epi16指令 4 次来limb单独计算每个 4 位。这可以以某种方式更快地完成吗?更准确地说:是否可以减少所需指令的数量?

cht*_*htz 2

我没有看到一种明显的方法来减少乘法次数,例如,屏蔽两个输入的足够字节以通过一次乘法获得两个单独的乘积。Evenvpmaddubsw很难利用,因为它需要一个操作数作为带符号的 8 位值(并且需要大量移位才能将半字节置于正确的位置)。

但是,您可以减少移动量,但需要进行更多屏蔽:

伪代码:

(a*b) & 0xf           = 0,0,0,ab
(a>>4)*(b&0xf0)       = *,*,ab,0
(a>>8)*(b&0xf00)      = *,ab,0,0
(a>>12)*(b&0xf000)    = ab,0,0,0
Run Code Online (Sandbox Code Playgroud)

使用内在函数(未经测试):

__m256i _mm256_mullo_epi4(const __m256i a, const __m256i b) {
    __m256i mask_000f = _mm256_set1_epi16(0x000f);
    __m256i mask_00f0 = _mm256_set1_epi16(0x00f0);
    __m256i mask_0f00 = _mm256_set1_epi16(0x0f00);
    __m256i mask_f000 = _mm256_set1_epi16(0xf000);
    __m256i tmp_mul_0 = _mm256_and_si256(_mm256_mullo_epi16(a, b), mask_000f);
    __m256i tmp_mul_1 = _mm256_and_si256(_mm256_mullo_epi16(_mm256_srli_epi16(a,  4), _mm256_and_si256(b, mask_00f0)), mask_00f0);
    __m256i tmp_mul_2 = _mm256_and_si256(_mm256_mullo_epi16(_mm256_srli_epi16(a,  8), _mm256_and_si256(b, mask_0f00)), mask_0f00);
    __m256i tmp_mul_3 =                  _mm256_mullo_epi16(_mm256_srli_epi16(a, 12), _mm256_and_si256(b, mask_f000));
    __m256i tmp1 = _mm256_xor_si256(tmp_mul_0, tmp_mul_1);
    __m256i tmp2 = _mm256_xor_si256(tmp_mul_2, tmp_mul_3);
    __m256i tmp  = _mm256_xor_si256(tmp1, tmp2);
    return tmp;
}
Run Code Online (Sandbox Code Playgroud)

与 4 次乘法、9 次移位和 7 位操作相比,这需要 4 次乘法和 3 次移位,但需要 9 次位操作(从技术上讲,屏蔽tmp_mul_3是不必要的,编译器可能能够将其优化掉)。

所以总共是 16 个微指令,而不是 19 个。