【问题标题】:SIMD versions of SHLD/SHRD instructionsSHLD/SHRD 指令的 SIMD 版本
【发布时间】:2017-01-09 15:20:41
【问题描述】:

SHLD/SHRD 指令是实现多精度移位的汇编指令。

考虑以下问题:

uint64_t array[4] = {/*something*/};
left_shift(array, 172);
right_shift(array, 172);

实现 left_shiftright_shift 这两个函数的最有效方法是什么?这两个函数对四个 64 位无符号整数数组进行移位操作,就好像它是一个大的 256 位无符号整数一样?

最有效的方法是使用 SHLD/SHRD 指令,还是在现代架构上有更好的(如 SIMD 版本)指令?

【问题讨论】:

  • 您正在为哪种架构编程?如果你在 x86 上,你可能有最高 SSE3 的指令 [编辑:正如@Ruslan 指出的,你可能在 32 位模式下支持 AVX/AVX2],或者在 x86_64 上最高 AVX2(除非你非常幸运并且能够大型英特尔协处理器上的 AVX512 程序)。如果您使用的是 ARM 并且支持 NEON,那么还有 SIMD 移位指令。
  • 取决于“172”是否固定,或者只是示例值:因为 172 是 21.5 个字节,允许您先将内容移动 21 个字节,然后将 11 个目标字节向右移动 4 次(即 3x shrd) 并用零清除其他 21 个字节。如果您的值已经在寄存器中,请检查此问题以获取许多资源:stackoverflow.com/q/25248766/4271923
  • @Dalton 您也可以在 32 位模式下使用 AVX2(不过,限制为 8 个 ymmN 寄存器,与 xmmN 一样)。
  • @Dalton 是的,它们都是前几代的扩展。这包括添加的ZMM16-ZMM31,仍然可以通过相应的YMMXMM 寄存器在下部访问。
  • @Dalton 不,所有这些都可用(例如vcvtpd2ps,这是cvtpd2ps 的明确VEX 编码版本,或新的vextractf128)。只是由于 VEX 被映射到另一条指令(在长模式下被删除)或未定义(导致#UD),一些操作码会引用更高的寄存器,如 YMM8,这意味着一些不同的东西。

标签: c assembly x86-64 bit-shift arbitrary-precision


【解决方案1】:

在这个答案中,我只会谈论 x64。
x86 已经过时了 15 年,如果您在 2016 年进行编码,那么停留在 2000 年几乎没有意义。
所有时间均以Agner Fog's instruction tables为准。

英特尔 Skylake 示例时序*
shld/shrd 指令在 x64 上相当慢。
即使在 Intel skylake 上,它们也有 4 个周期的延迟并使用 4 微指令,这意味着它会占用大量执行单元,在较旧的处理器上它们甚至更慢。
我会假设你想改变一个可变的数量,这意味着一个

SHLD RAX,RDX,cl        4 uops, 4 cycle latency.  -> 1/16 per bit

使用 2 班次 + 加法,您可以更快 更慢地做到这一点。

@Init:
MOV R15,-1
SHR R15,cl    //mask for later use.    
@Work:
SHL RAX,cl        3 uops, 2 cycle latency
ROL RDX,cl        3 uops, 2 cycle latency
AND RDX,R15       1 uops, 0.25 latency
OR RAX,RDX        1 uops, 0.25 latency    
//Still needs unrolling to achieve least amount of slowness.

请注意,这仅移动 64 位,因为 RDX 不受影响。
所以你试图每 64 位击败 4 个周期。

//4*64 bits parallel shift.  
//Shifts in zeros.
VPSLLVQ YMM2, YMM2, YMM3    1uop, 0.5 cycle latency.  

但是,如果您希望它与 SHLD 完全一样,则需要使用额外的 VPSLRVQ 和 OR 来组合这两个结果。

VPSLLVQ YMM1, YMM2, YMM3    1uop, 0.5 cycle latency.  
VPSRLVQ YMM5, YMM2, YMM4    1uop, 0.5 cycle latency.   
VPOR    YMM1, YMM1, YMM5    1uop, 0.33 cycle latency.   

您需要交错其中的 4 组,这会花费您 (3*4)+2=14 个 YMM 寄存器。
这样做我怀疑您是否会从 VPADDQ 的 0.33 低延迟中获益,因此我将假设为 0.5 延迟。
这使得 256 位的 3uops、1.5 周期延迟 = 每比特 1/171 = 每 QWord 0.37 周期 = 快 10 倍,还不错。
如果您能够获得每 256 位 1.33 个周期 = 每位 1/192 = 每 QWord 0.33 个周期 = 快 12 倍。

'It’s the Memory, Stupid!'
显然,我没有添加循环开销和加载/存储到/从内存中。
考虑到跳转目标的正确对齐,循环开销很小,但内存
访问很容易成为最大的放缓。
Skylake 上主内存的单个缓存未命中可能会花费您 more than 250 cycles1
主要的收获在于巧妙地管理内存。
相比之下,使用 AVX256 的 12 倍可能加速是小菜一碟。

我没有计算CL/(YMM3/YMM4) 中移位计数器的设置,因为我假设您将在多次迭代中重复使用该值。

您不会使用 AVX512 指令来击败它,因为带有 AVX512 指令的消费级 CPU 尚不可用。
当前唯一支持的处理器是Knights Landing

*) 所有这些时间都是最佳情况值,应被视为指示,而不是硬值。
1) Skylake 中缓存未命中的成本:42 个周期 + 52ns = 42 + (52*4.6Ghz) = 281 个周期。

【讨论】:

  • 只是简单地说,Skylake 上的内存缓存未命中不如 1000 个周期(除非计算页面错误)。只有当它是一个非常远程的 NUMA 节点的缓存未命中时,才会发生这种情况。但这在 atm 上是不可能的,因为多路 Skylake 服务器还没有发布。
  • 呵呵,在 SKL 上,VPSLLVQ 比普通的 VPSLLQ 更有效(它只从底部元素获取移位计数),这真的很奇怪。看起来 SKL 的 VPSLLQ 使用 port5 shuffle 将移位计数广播到向量的每个元素,然后将其馈送到 VPSLLVQ 执行单元。在 BDW 和更早版本上,VPSLLQ 也采用 port5 uop,但 VPSLLVQ 甚至更慢。无论如何,对于立即计数移位(这可能在内联后很常见),VPSLLQ v, v, i 绝对是最有效的方法。
  • 顺便说一句,您应该使用 VPOR,而不是 VPADDQ,以获得更好的 pre-SKL 吞吐量。另外,我认为您缺少在元素之间移动数据的任何说明。较大的移位计数可以将数据从第一个 qword 移动到最后一个 qword。如果数据不在寄存器中,则未对齐的加载可能会很好;那么您只需要处理最多 7 或 63 的移位计数。(您可以使用立即计数字节移位或其他东西,而不是从表中查找随机掩码。)
猜你喜欢
  • 1970-01-01
  • 2019-08-29
  • 2016-08-28
  • 1970-01-01
  • 1970-01-01
  • 2021-06-29
  • 2014-06-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多