RTC*_*222 1 assembly sse x86-64 nasm 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]
Run Code Online (Sandbox Code Playgroud)
更新#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
Run Code Online (Sandbox Code Playgroud)
请参阅Agner Fog的asm优化指南,他在SIMD上的章节中有一张随机播放指令表,列出了各种数据移动方式,这些指令可以让您考虑一些指令(或者,如果您不记得它们的确切内容,请查阅Intel手册) ),看看它们是否就是您想要的。
向这两个元素广播寄存器的高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代替。
同样是re:code-size:使用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或vpshufd解决您要解决的问题。这是浪费代码的大小,因为它需要立即执行,但是显然您没有意识到可以shufpd xmm0, xmm0, 0b11用来(按此顺序):
xmm0[1](第一个src操作数,立即数的低位)的低位qwordxmm0[1](第二个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与PD的性能指令,而是关心vec-int与vec-FP的指令,因此不幸的是pshufd,复制和改组FP数据并不总是胜利。或shufps用作2源整数洗牌。
不幸的是,在AVX512之前,没有通用的2源“整数”改组,palignr也没有punpck说明。在AVX之前,没有FP复制和改组说明。(而且具有讽刺意味的是,除了内存源负载+随机播放之外,vpermilps立即数是多余的vshufps dst, same,same, imm8,并且出于代码大小的原因应避免 使用。VPERMILPS指令(_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
Run Code Online (Sandbox Code Playgroud)
这是有效的改组,但是不幸的是,它在第一个Round的输出和第二个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
Run Code Online (Sandbox Code Playgroud)
除非您的手写Round函数不会碰到的寄存器不足,否则您就不需要特别的内存并且效率也不高。
另外,所有这些movaps和movhlps指令仅3个字节长,并且与您版本中的指令数量相同。
另一个选择(尤其是如果您输入的内容在不同的寄存器中开头)将是Round第一个高位,然后您可以使用将高位返回到xmm0 movlhps。
而且,如果您具有SSE4.1,roundpd则BTW 可以四舍五入为具有Nearest的最接近整数,向+ -Inf(上限/下限)或接近0(截断)。
movsd [scratch_register+8],xmm0 ; write low qword to memory
movupd xmm11,[scratch_register]
Run Code Online (Sandbox Code Playgroud)
永远不要这样做,狭窄的仓库+宽的装货量是保证的仓库转发停滞。(约10个周期的额外延迟)。
使用16字节对齐的存储位置(例如,在堆栈上[rsp+8]或其他位置),然后
unpckhpd xmm0, [scratch_register]加载+随机播放。
不幸的是,英特尔对内存源unpck指令的设计很差,因此它们需要16字节的内存源,而不仅仅是它们实际加载/使用的8字节。在几种情况下