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

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

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

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

SHUFPD洗牌。

VPSLLDQ使高四字变为零。

是否有指令将浮点值从xmm寄存器的高四字移动到同一xmm寄存器或另一个xmm寄存器的低四字?或者我是否总是要经历记忆(增加额外的周期)?

更新:根据@fuz和@Peter Cordes的评论,这就是我的所作所为。这为xmm0的低和高四字分别调用了舍入函数;由于特殊的舍入参数,必须单独为每个qword调用该函数,因此它不能是SIMD指令。目标是围绕xmm0中的每个qwords并将结果放在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
assembly x86-64 nasm sse avx
1个回答
5
投票

请参阅Agner Fog's asm optimization guide,他关于SIMD的章节有一个随机指示表,不同类型的数据移动将为您提供少量的思考指示(或者如果您不记得他们的确切行为,请查阅英特尔的手册)并查看如果他们是你想要的。


向两个元素广播寄存器的高qword的最便宜方式是movhlps xmm0,xmm0。 (或者对于整数数据,如果您的代码可能在Nehalem上运行,请使用punpckhqdq xmm0,xmm0来避免FPvec-int绕过延迟。)

没有AVX,movhlps很好,因为它与unpckhpd略有不同。

  • movhlps xmm3, xmm4xmm3[0] = xmm4[1];,留下xmm3[1]不变。
  • unpckhpd xmm3, xmm4从xmm3和xmm4获取高qwords并按顺序将它们放入xmm3。所以在目的地,高qword移动到低,然后src的高qword被复制。 xmm3[0] = xmm3[1]; xmm3[1] = xmm4[1]

unpcklpd是无用的,它长1个字节,与SSE1 movlhps做同样的事情。 (将低qword从src复制到目标的高qword,使目标的低qword保持不变。)对于movapd,总是使用movaps

另外:代码大小:使用xmm8..15花费REX前缀,因此选择你的寄存器分配以尽可能少的指令使用xmm8..15(或者已经需要REX前缀的指令,例如指针在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操作数,直接的高位)。

shuffle控件可以多次读取相同的输入元素。


有趣的是,NASM编译器只用两个操作数编译VUNPCKHPD

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

我很困惑,因为这些值是双精度,而不是单一,但它的工作原理。

一切都只是要复制的位/字节。除非您使用FP计算指令(例如addpd / addps),否则“类型”无关紧要。 (您可以通过手册条目中是否存在“SIMD浮点异常”部分来判断它是否关注位作为FP位模式的含义。例如addpshttps://www.felixcloutier.com/x86/addps#simd-floating-point-exceptions。(但是没有'任何惊喜。唯一关心的指令是出于非常明显的原因这样做,例如进行FP计算或类型转换,而不仅仅是复制数据。)

没有真正的CPU关心PS和PD指令的性能,但有些关心vec-int与vec-FP,所以不幸的是,使用pshufd来复制和改组FP数据并不总是一个胜利。或者使用shufps作为2源整数shuffle。

不幸的是,在AVX512之前没有通用的2源“整数”shuffle,只有palignrpunpck指令。在AVX之前,没有FP copy-and-shuffle指令。 (具有讽刺意味的是,除了内存源加载+随机播放之外,带有立即数的vpermilpsvshufps dst, same,same, imm8是冗余的,并且应该避免出于代码大小的原因.What's the point of the VPERMILPS instruction (_mm_permute_ps)?


  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

这是有效的改组,但不幸的是,它在第​​一轮的输出和第二轮的输入之间产生了错误的依赖关系。所以这两个调用不能并行工作。相反,在第一次调用之前复制时进行随机播放,最好是进入一个你知道已经“死”一段时间的寄存器,或者是xmm0中值的依赖链的一部分,所以必须在它之前准备好。

  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

除非你的手写Round函数没有触及寄存器,否则你并不特别需要内存,而且效率也不高。

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

另一个选择(特别是如果你的输入在一个不同的寄存器中开始)将是Round高半,然后你可以用movlhps将高半部分放回xmm0。

顺便说一句,如果你有SSE4.1,roundpd可以舍入到最近的整数,最近,朝向+ -Inf(ceil / floor),或朝0(截断)。


movsd [scratch_register+8],xmm0 ; write low qword to memory
movupd xmm11,[scratch_register]

从不这样做,狭窄的商店+广泛的重新加载是一个保证的商店转发摊位。 (约10个循环额外延迟)。

使用16字节对齐的存储位置(例如,在[rsp+8]或其他东西的堆栈上),和 unpckhpd xmm0, [scratch_register]加载+ shuffle。

不幸的是,英特尔严重设计了内存源解析指令,因此它们需要一个16字节的内存源,而不仅仅是它们实际加载/使用的8个字节。有几种情况

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