AVX 512 内在函数添加 128 位元素的 512 位

问题描述 投票:0回答:1

我在 Intel Intrinsic 指南中看不到这一点,但也许我错过了它。

如果我有两个 512 位寄存器

a
b
我想将它们视为具有四个 128 位元素,然后执行:

a[0] + b[0]

a[1] + b[1]

a[2] + b[2]

a[3] + b[3]

这样的AVX 512指令存在吗?

performance optimization x86 intel simd
1个回答
0
投票

不,任何数学运算的最宽元素大小都是 64 位。 AVX-512 具有无符号整数比较,因此您可以轻松生成具有进位的元素掩码 (

carry = (a+b) < a
)。

然后可以移动掩码 (

kshiftlb
) 并将其用于合并掩码
add
(或
sub
-1
)以递增低半部分已进位的元素的高 64 位。 (使用
_mm512_setr_epi64(0, -1, 0, -1, 0, -1, 0, -1)
,因此从高半部分进位会将
0
添加到下一个 128 位元素的低半部分,因此您不必担心跨元素边界移动进位。)
set1(-1)
的成本很低生成,但编译器可能不太擅长使用
0, -1
/
vpternlogq
vpslldq zmm, zmm, 8
模式,因此您可能只使用
add
0, 1
模式。

或者也许是其他一些技巧,也许是在向量寄存器中生成

0
-1
并随机播放,而不是通过掩码寄存器? 但饱和减法仅适用于 8 或 16 位元素。

与单个 BigInt 添加的主要区别在于,每个进位只能传播一步,而不是一直传播到最后,因此不存在长串行依赖性。

#include <immintrin.h>

__m512i add_epi128(__m512i a, __m512i b)
{
   __m512i sum = _mm512_add_epi64(a, b);
   __mmask8 carry = _mm512_cmplt_epu64_mask(sum, b);  // a or b doesn't matter, but compilers don't realize that. 
                                                     // For the standalone function, using b lets them overwrite a in ZMM0
                                                     // Of course in reality you want this function to inline.

   //carry = _kshiftli_mask8(carry, 1);  // or actually just kaddb is more compact, but compilers miss the optimization
   carry = _kadd_mask8(carry, carry);
   //carry += carry;

   const __m512i high_ones = _mm512_setr_epi64(0, -1, 0, -1, 0, -1, 0, -1);
   // Carry-propagation into the high half of each u128, with merge-masking
   sum = _mm512_mask_sub_epi64(sum, carry, sum, high_ones);
   return sum;
}

kaddb
kshiftlb
在 Intel 上都有 4 个周期延迟,在 Zen 4 上有 1 个周期延迟 (https://uops.info/),但
kaddb
短了一个字节(没有立即数)。 我必须使用内在函数来获取 GCC 并 clang 来发出它而不是
kshiftlb
。 (神箭)

# clang 18 -O3 -march=x86-64-v4
.LCPI0_0:
        .quad   0
        .quad   1
          ...
add_epi128(long long vector[8], long long vector[8]):
        vpaddq  zmm0, zmm1, zmm0
        vpcmpltuq       k0, zmm0, zmm1
        kaddb   k1, k0, k0
        vpaddq  zmm0 {k1}, zmm0, zmmword ptr [rip + .LCPI0_0]
        ret

假设将向量内联并保持在寄存器中,则对于前端和后端执行单元来说,这是 4 个微指令。 从两个向量输入准备就绪起,英特尔的关键路径延迟约为 10 个周期。 (1 表示

vpaddq
指令,即使有合并掩码。4 或 3 表示比较掩码,Zen4 上为 5。4 表示
kaddb
,英特尔上 vs. Zen4 上的 1。)

© www.soinside.com 2019 - 2024. All rights reserved.