乘以 32 位整数的向量,只取高 32 位

Multiply vectors of 32 bit integers, taking only high 32 bits

我想将 16 个无符号 32 位整数的两个 512 位 __m512i 向量相乘,并且只取 ​​64 位乘法结果的高 32 位。尽管英特尔内部函数指南说 _mm512_mulhi_epu32 存在,但它无法在我的机器上编译。

答案 声称 _mm512_srli_epi64(_mm512_mul_epu32(a,b),32) 会起作用,但它不起作用 - 问题似乎是 _mm512_mul_epu32 仅涉及位 0...31、64。 ..95 等,忽略奇数位置的值。

如何最快地从 32 位向量乘法的结果中取出高 32 位?

vpmuludq 又名 _mm512_mul_epu32 采用偶数源 32 位元素(0、2、4 等)1。这使其能够高效执行,在每个 64 位块内将输入的低 32 位提供给 FP 尾数乘法器。这是一个扩大的全乘法,而不是高半乘法,所以它当然必须忽略一些输入(因为没有 SIMD 数学指令有两个向量目的地。)

所以你需要使用它两次才能得到你想要的所有高半部分结果:一次是偶数元素,一次是偶数位置的奇数元素(右移动两个输入向量)。然后你需要交错那些 64 位元素的高半部分。

诀窍是高效地做到这一点:AVX-512 vpermt2d 从 2 个源向量中挑选 32 位元素,在一个 uop 中完成工作。这很好,尤其是在允许编译器提升洗牌控制向量常量负载的循环中。其他选项包括 _mm512_mask_shuffle_epi32 (vpshufd with merge-masking) to copy the high halves down in 1 vector, and merge into the other vector of results, given a merge-control in a k register. (One of the vpmuludq results has the high halves where you want them, because the inputs were right shifted). vmovshdup (_mm512_mask_movehdup_ps) 在少 1 个字节的机器代码中进行相同的洗牌,不需要立即使用。它对内部函数不方便,因为你需要转换 __m512i__m512_mm512_castsi512_ps,但应该具有相同的性能。

甚至存储两次,对第二个存储进行屏蔽,但这可能很糟糕,因为其中一个存储必须未对齐(因此 64 字节存储的缓存行交叉)。不过,它确实避免了 any 更多的 ALU uops。

更“明显”的选项(就像您对 AVX2 所做的那样)是 vpsrld (_mm512_srli_epi64(v,32)) 其中之一,然后是 vpblendd。但这需要 2 个单独的 ALU 微指令,并且在当前 CPU 上使用 512 位向量意味着只有 2 个向量 ALU 执行端口可以处理它们。此外,vpblendd 没有 AVX-512 版本;只有混合在 k 寄存器中获取控制操作数。 (用shift / AND 和 OR 合并会更糟,还需要一个向量常数)

__m512i mulhi_epu32_512(__m512i a, __m512i b)
{
    __m512i evens = _mm512_mul_epu32(a,b);
    __m512i odds = _mm512_mul_epu32(_mm512_srli_epi64(a,32), _mm512_srli_epi64(b,32));
    return _mm512_mask_shuffle_epi32(odds, 0x5555, evens, _MM_SHUFFLE(3,3,1,1)); 

    // _mm512_mask_movehdup_ps may be slightly more efficient, saving 1 byte of code size
}

对于独立函数,clang 将合并掩码洗牌优化为 vpermi2d,其中包含来自内存的向量常量,而不是 mov eax, 0x5555 / kmovw k1, eax 或其他任何内容。包含安装程序时微指令更少,但可能缓存未命中。 GCC 按编写的方式编译它。 https://godbolt.org/z/v4M7PK shows both. For a loop body (with setup hoisted), either way is a single uop, but merge-masked vpshufd has only 1 cycle of latency, vs. 3 for lane-crossing vpermi2d / vpermt2d. (https://uops.info/ and https://agner.org/optimize/)


脚注 1: 您链接的问答没有完全描述问题 and/or 解决方案,或者实际上只需要 2 个数字(在向量的底部?),而不是数字的 2 向量