【问题标题】:practical BigNum AVX/SSE possible?实用的 BigNum AVX/SSE 可能吗?
【发布时间】:2015-03-11 11:18:07
【问题描述】:

SSE/AVX 寄存器可以被视为整数或浮点 BigNums。也就是说,人们完全可以忽略存在车道。是否存在一种简单的方法来利用这种观点并将这些寄存器单独或组合用作 BigNums?我问是因为从我对 BigNum 库的了解来看,它们几乎普遍地在数组上存储和执行算术运算,而不是在 SSE/AVX 寄存器上。便携性?

例子:

假设您将 SSE 寄存器的内容作为密钥存储在 std::set 中,您可以将这些内容作为 BigNum 进行比较。

【问题讨论】:

  • 当然有可能,只是非常不方便、效率低下而且速度慢。当您对 limbs(32/64 位字)数组进行加法时,很容易使用 x86 进位标志来传播进位位。 SSE 寄存器的通道没有有进位标志,这意味着必须以不同的方式检测溢出(计算量更大),即使你确实检测到溢出,你也会遇到复杂的 SSE/ AVX 洗牌以提升进位,您必须为N-limb bignums 执行此操作N-1 次。那么如果你需要将一个 bignum 扩展到 128 位/256 位之外会发生什么...?
  • 您使用 gcc/clang/icc vector extensions 将 2 个或更多寄存器连接在一起。你可以写一个答案,为什么你认为这是不切实际的。问题是,我认为 gcc 将数组映射到 SIMD 寄存器很糟糕,但它很容易将 SIMD 寄存器映射到相反的方向并且没有任何问题。
  • 您链接到的不是在运行时动态扩展 bignum 的方法。您链接到的是一种在编译时声明有限和固定大小向量(带通道)的方法。您仍然遇到我上面列出的完全相同的问题,并且您仍然无法使用这些扩展轻松检测和传播从下肢到上肢的携带。
  • 好的,+1,请写一个答案,我会接受。

标签: sse biginteger simd avx extended-precision


【解决方案1】:

我认为可以有效地使用 SIMD 实现 BigNum,但不是按照您建议的方式。

您应该一次处理多个 BigNum,而不是使用 SIMD 寄存器(或使用 SIMD 寄存器数组)实现单个 BigNum。

让我们考虑 128 位加法。让 128 位整数由一对高和低 64 位值定义,假设我们要将 128 位整数 (y_low, y_high) 添加到 128 位整数 (x_low, x_high)。使用 64 位标量寄存器,这只需要两条指令

add rax, rdi // x_low  += y_low;
adc rdx, rsi // x_high += y_high + (x_low < y_low);

正如其他人所解释的,对于 SSE/AVX,问题在于没有 SIMD 进位标志。必须计算进位标志然后添加。这需要 64 位无符号比较。对于 SSE,唯一现实的选择是来自 AMD XOP 指令 vpcomgtuq

vpaddq      xmm2, xmm0, xmm2 // x_low  += y_low;
vpcomgtuq   xmm0, xmm0, xmm2 // x_low  <  y_low
vpaddq      xmm1, xmm1, xmm3 // x_high += y_high
vpsubq      xmm0, xmm1, xmm0 // x_high += xmm0

这使用四个指令来添加两对 128 位数字。对于 64 位标量寄存器,这也需要 4 条指令(两条 add 和两条 adc)。

使用 AVX2,我们可以一次添加四对 128 位数字。但是 XOP 没有 256 位宽的 64 位无符号指令。相反,我们可以为a&lt;b 执行以下操作:

__m256i sign64 = _mm256_set1_epi64x(0x8000000000000000L);
__m256i aflip = _mm256_xor_si256(a, sign64);
__m256i bflip = _mm256_xor_si256(b, sign64);
__m256i cmp = _mm256_cmpgt_epi64(aflip,bflip);

sign64 寄存器可以预先计算,因此只需要三个指令。因此,用 AVX2 加四对 128 位数字可以用 6 条指令完成

vpaddq
vpaddq
vpxor
vpxor
vpcmpgtq 
vpsubq

而标量寄存器需要八条指令。

AVX512 有一条指令用于进行 64 位无符号比较 vpcmpuq。因此,应该可以仅使用 4 条指令将 8 对 128 位数字相加

vpaddq
vpaddq
vpcmpuq
vpsubq

使用标量寄存器需要 16 条指令来添加八对 128 位数字。

这是一个表格,其中汇总了 SIMD 指令(称为 nSIMD)的数量和添加 128 位数字对(称为 npairs)所需的标量指令(称为 nscalar)的数量

              nSIMD      nscalar     npairs
SSE2 + XOP        4           4           2
AVX2              6           8           4
AVX2 + XOP2       4           8           4
AVX-512           4          16           8

请注意,XOP2 还不存在,我只是推测它可能在某个时候存在。

还要注意,要有效地执行此操作,BigNum 数组需要存储在数组结构 (AoSoA) 形式的数组中。例如,使用l 表示低 64 位,使用h 表示高 64 位,一个 128 位整数数组存储为这样的结构数组

lhlhlhlhlhlhlhlh

应该使用像这样的 AoSoA 来存储

SSE2:   llhhllhhllhhllhh
AVX2:   llllhhhhllllhhhh
AVX512: llllllllhhhhhhhh

【讨论】:

  • 和我想的一样。随着 SIMD 寄存器变得越来越大,它们中的 BigNum(以及其他所有东西)的空间将越来越大,而对它们进行算术运算所需的指令将越来越少。此外,寄存器存储器是程序员可用的最快存储器。你能澄清一下你的表吗?
  • 很好看 (+1):切片。但是:1)128 位整数是扩展(大)整数固定精度类型,不一定是任意精度类型。 2)这里没有计算额外的开销和运气。 n bignums 具有相同大小和数量的肢体并希望对每个肢体应用相同的操作的频率是多少?如何有效地将它们加载到寄存器中?所需的加载/(去)交错/插入/提取/存储的数量意味着盈亏平衡点比您的表格建议的要高。 VGATHER 可以,但 AVX2 很少见,因为 3) 是 AMD 最新产品之外的 XOP
  • @IwillnotexistIdonotexist,假设 128 位整数以 AoSoA 形式存储(例如使用 AVX2 lolololohihihihihi),可以有效地完成加载。您是什么意思“是 AMD 最新产品之外的 XOP”。这应该是一个问题吗?一般来说,你的答案更好。我主要是想找到一个 SIMD 对大整数精度类型有用的案例。如果精度不固定,则可以在 SIMD 宽度块中为它们中的每一个使用最大精度。
  • 你会很难相信这一点,但为了记录,我找到了一种方法来(水平)矢量化 bignum add。 IOW,将两个__m512i 加在一起,就好像它们是 512 位整数一样。它可以在 10 条以下指令中完成,关键路径短到足以限制吞吐量。 IOW,它可能会击败一连串的 8 个 add-with-carry 指令。不幸的是,它确实需要 AVX512-DQ 才能高效。这个想法基于 Kogge-Stone Adder。但现在我仍在研究细节(和证明),我想先在 y-cruncher 上用真正的硬件试驾一下。
  • 如果你有兴趣,我已经写了一篇关于here 方法的博客。我最终能够(非正式地)证明它是正确的。但我研究得越多,它的用处就越少。
【解决方案2】:

移自上面的评论

可以这样做,但没有这样做,因为在向量寄存器中实现 bignums 不是特别方便。

对于简单的加法任务,使用 x86 EFLAGS/RFLAGS 寄存器的进位标志从最低的“肢体”向上传播加法的进位(使用GMP 术语)是微不足道的,并循环遍历任意数量的肢体放置在一个数组中。

相反,SSE/AVX 寄存器的通道没有进位标志,这意味着必须以不同的方式检测溢出,包括比较以检测回绕,这在计算上更加密集。此外,如果在一个肢体中检测到溢出,则必须通过丑陋的 shuffle “向上”传播,然后添加,这种添加可能会导致另一个溢出和结转,对于 @ 最多 N-1 次987654323@-limb bignum。然后,一旦 sum 使 bignum 超过 128 位/256 位(或超过 128 位 x # 个寄存器),您无论如何都必须将其移动到数组中。

因此,将需要很多特殊情况的代码,而且它不会更快(事实上,慢得多),只是为了添加。想象一下乘法需要什么?还是喘气,分裂?

【讨论】:

    【解决方案3】:

    有可能,但不实用。

    正如我在the other answer 中所说,AVX/SSE 中没有进位标志,因此不可能有效地进行加法和减法。并且要进行乘法运算,您需要大量改组才能将扩大的乘法结果放在所需的位置。

    如果允许您使用较新的 Haswell/Broadwell 微架构,则解决方案将是 BMI2 中的 MULX 和 ADX 中的 ADOX, ADCX。您可以阅读他们的相关信息here

    【讨论】:

    • 能否请您回答我的其他question,与您的回答有关。
    猜你喜欢
    • 1970-01-01
    • 2015-07-21
    • 1970-01-01
    • 1970-01-01
    • 2017-07-25
    • 2016-10-29
    • 1970-01-01
    • 2013-08-21
    • 1970-01-01
    相关资源
    最近更新 更多