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
请参阅Agner Fog's asm optimization guide,他关于SIMD的章节有一个随机指示表,不同类型的数据移动将为您提供少量的思考指示(或者如果您不记得他们的确切行为,请查阅英特尔的手册)并查看如果他们是你想要的。
向两个元素广播寄存器的高qword的最便宜方式是movhlps xmm0,xmm0
。 (或者对于整数数据,如果您的代码可能在Nehalem上运行,请使用punpckhqdq xmm0,xmm0
来避免FPvec-int绕过延迟。)
没有AVX,movhlps
很好,因为它与unpckhpd
略有不同。
movhlps xmm3, xmm4
做xmm3[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位模式的含义。例如addps
:https://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,只有palignr
和punpck
指令。在AVX之前,没有FP copy-and-shuffle指令。 (具有讽刺意味的是,除了内存源加载+随机播放之外,带有立即数的vpermilps
与vshufps 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函数没有触及寄存器,否则你并不特别需要内存,而且效率也不高。
作为奖励,所有这些movaps
和movhlps
指令只有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个字节。有几种情况