将浮点数从高xmm四字移动到低xmm四字

时间:2019-04-19 16:46:18

标签: assembly x86-64 nasm sse avx

MOVHPD将xmm寄存器的高四字提取到内存中。

PEXTRQ提取xmm寄存器的高位四字并将其放入整数寄存器(仅整数)。

SHUFPD随机播放。

VPSLLDQ使高位四字清零。

是否有指令将浮点值从xmm寄存器的高位四字移动到同一xmm寄存器或另一个xmm寄存器的低位四字中?还是我总是必须经过内存(添加额外的周期)?

更新: 根据@fuz和@Peter Cordes在下面的评论,这就是我所做的。这将分别为xmm0的上下四位数调用舍入函数;由于特殊的舍入参数,必须为每个qword分别调用该函数,因此它不能是SIMD指令。目标是将xmm0中的每个qword取整并将结果放入xmm11中。

movapd xmm2,xmm0 ;preserve both qwords of xmm0
call Round
movsd [scratch_register+0],xmm0 ; write low qword to memory
movhlps xmm0,xmm2
call Round
movsd [scratch_register+8],xmm0 ; write low qword to memory
movupd xmm11,[scratch_register]

更新#2: @Peter Cordes展示了如何在没有记忆的情况下做到这一点:

movhlps  xmm2, xmm0   ; extract high qword for later
call Round            ; round the low qword
movaps   xmm3, xmm0   ; save the result
movaps   xmm0, xmm2   ; set up the arg
call Round            ; round the high qword
movlhps  xmm3, xmm0   ; re-combine into xmm3

1 个答案:

答案 0 :(得分:5)

请参阅Agner Fog's asm optimization guide,他在SIMD上的章节中有一张随机播放指令表,列出了各种数据移动方式,这些指令可以让您考虑一些指令(如果您不记得的话,可以在英特尔手册中查找)确切地知道他们在做什么),看看他们是否正是您想要的。


向两个元素广播寄存器高位qword的最便宜方法是 movhlps xmm0,xmm0(对于整数数据,如果您的代码可能在Nehalem上运行,请使用punpckhqdq xmm0,xmm0以避免FP <-> vec-int旁路延迟。)

在没有AVX的情况下,movhlps很不错,因为它的随机播放与unpckhpd略有不同。

  • movhlps xmm3, xmm4执行xmm3[0] = xmm4[1];,而使xmm3[1]保持不变。
  • unpckhpd xmm3, xmm4从xmm3和xmm4中获取高位qword,并将其按顺序放入xmm3中。因此,在目标中,高qword移到低,然后将src中的高qword复制过来。 xmm3[0] = xmm3[1]; xmm3[1] = xmm4[1]

但是unpcklpd没用,它长了1个字节,并且与SSE1 movlhps的作用相同。 (将src中的低位qword复制到目标的高位qword,保留目标的低位qword不变。)与movapd相同,请始终使用movaps

也:代码大小:使用xmm8..15需要花费REX前缀,因此请选择寄存器分配以在尽可能少的指令(或已经需要REX前缀的指令)中使用xmm8..15。用于r8..15中的指针)。代码大小通常并不重要,但是其他所有条件通常都较小。通常,较小的指令可以更好地包装到uop缓存中。


对于AVX,您可以将vunpckhpd与源操作数的任意顺序一起使用,第一个src的高qword会到达目标的低qword。 vmovhlps没有代码大小优势(或其他性能优势),它们都可以使用2字节的VEX前缀来实现最小4字节的指令大小。

例如vunpckhpd xmm0, xmm1, xmm0就像vmovhlps xmm0, xmm0,xmm1


您可以使用shufpd or vpshufd解决您要解决的问题。浪费代码大小,因为它需要立即执行,但是显然您没有意识到可以使用shufpd xmm0, xmm0, 0b11来获取(按此顺序):

  • xmm0[1]中的低位qword(第一个src操作数,立即数的低位)
  • xmm0[1]中的高位qword(第二个src操作数,立即数的高位)。

随机播放控件可以多次读取同一输入元素。


  

有趣的是,NASM编译器将仅使用两个操作数来编译VUNPCKHPD

NASM允许您将vaddps xmm0, xmm0, xmm1之类的指令编写为vaddps xmm0, xmm1,当它与第一个源相同时,省略单独的目标操作数。

  

我很困惑,因为这些值是双精度的,不是单精度的,但是可以用。

一切都只是要复制的位/字节。除非您使用FP计算指令(例如addpd / addps),否则“类型”无关紧要。 (您可以通过手动输入中是否存在“ SIMD浮点异常”部分来判断是否将位的含义当作FP位模式来对待。例如addps:  https://www.felixcloutier.com/x86/addps#simd-floating-point-exceptions。 (但是,这并不令人感到意外。唯一关心的指令之所以这样做是因为很明显的原因,例如进行FP计算或类型转换,而不仅仅是复制数据。)

没有真正的CPU关心PS vs. PD指令的性能,但有些关心vec-int与vec-FP的关系,因此不幸的是,使用pshufd复制和改组FP并不总是一个胜利。数据。或将shufps用作2源整数随机播放。

不幸的是,在AVX512之前,没有通用的2源“整数”改组,只有palignrpunpck指令。在AVX之前,没有FP复制和改组说明。 (而且具有讽刺意味的是,vpermilpsvshufps dst, same,same, imm8相比, movapd xmm2,xmm0 ;preserve both qwords of xmm0 call Round movsd [scratch_register+0],xmm0 ; write low qword to memory movhlps xmm0,xmm2 call Round 是多余的,除了内存源负载+随机播放,并且出于代码大小的原因,应避免使用。What's the point of the VPERMILPS instruction (_mm_permute_ps)?


  movhlps  xmm2, xmm0   ; extract high qword for later
  call Round                ; round the low qword
  movaps   xmm3, xmm0   ; save the result
  movaps   xmm0, xmm2   ; set up the arg
  call Round                ; round the high qword
  movlhps  xmm3, xmm0    ; re-combine into xmm3

这是有效的改组,但不幸的是,它在第一个回合的输出和第二个回合的输入之间创建了错误的依赖关系。因此,这两个调用不能并行运行。相反,在第一次调用之前复制时应随机洗,最好是进入已知已经“死”一段时间的寄存器,或者是xmm0中的值的依赖链的一部分,因此必须先准备好。

movaps

除非您的手写Round函数不会碰到的寄存器不足,否则您不需要特别的内存并且效率也不高。

作为奖励,所有这些movhlpsRound指令都只有3个字节长,并且它们的数量与您的版本中的指令相同。

另一种选择(尤其是如果您的输入位于不同的寄存器中),首先是movlhps的上半部分,然后您可以使用roundpd将上半部分放回到xmm0中。 / p>

而且顺便说一句,如果您拥有SSE4.1,则movsd [scratch_register+8],xmm0 ; write low qword to memory movupd xmm11,[scratch_register] 可以四舍五入为最接近的整数,向+ -Inf(上限/下限)或0(截断)。


[rsp+8]

永远不要这样做,狭窄的仓库+宽的装货量是保证的仓库转发停顿。 (约10个周期的额外延迟)。

使用16字节对齐的存储位置(例如,在堆栈上的unpckhpd xmm0, [scratch_register]或其他位置),
{{1}}以加载和随机播放

不幸的是,英特尔对内存源unpck指令的设计很糟糕,因此它们需要一个16字节的内存源,而不仅仅是它们实际加载/使用的8个字节。在几种情况下