将浮点数从高 xmm 四字移动到低 xmm 四字
Posted
技术标签:
【中文标题】将浮点数从高 xmm 四字移动到低 xmm 四字【英文标题】:Move float from high xmm quadword to low xmm quadword 【发布时间】:2019-04-19 16:46:18 【问题描述】:MOVHPD 将 xmm 寄存器的高位四字提取到内存中。
PEXTRQ 提取 xmm 寄存器的高位四字并将其放入整数寄存器(仅限整数)。
SHUFPD 随机播放。
VPSLLDQ 使高位四字清零。
是否有将浮点值从 xmm 寄存器的高位四字移动到同一 xmm 寄存器或另一个 xmm 寄存器的低位四字的指令?还是我总是必须通过记忆(添加额外的周期)?
更新: 基于@fuz 和@Peter Cordes 下面的 cmets,这就是我所做的。这会分别为 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
【问题讨论】:
我认为unpckhpd
应该可以解决问题。
谢谢——我现在就去看看。
你确定这段代码是正确的? vunpckhpd
有三个操作数,所以看起来很奇怪。此外,这绝对看起来不正确。 unpckhpd
和 vunpckhpd
应该做你期望他们做的事情。也许你想要vunpcklpd
或unpcklpd
代替?
您可以使用movhlps
。
了解这些指令只是移动字节。它们代表什么并不重要。
【参考方案1】:
请参阅Agner Fog's asm optimization guide,他关于 SIMD 的章节有一张 shuffle 指令表他们这样做),看看他们是否是你想要的。
将寄存器的高位 qword 广播到两个元素的最便宜的方法是 movhlps xmm0,xmm0
。(或者对于整数数据,如果您的代码可能在 Nehalem 上运行,请使用 punpckhqdq xmm0,xmm0
以避免 FPvec-int 绕过延迟。)
没有 AVX,movhlps
很好,因为它的 shuffle 与 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
做同样的事情。 (将 src 中的低 qword 复制到目标的高 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 操作数,立即数的高位)。
随机播放控件可以多次读取同一个输入元素。
有趣的是,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 copy-and-shuffle 指令。 (具有讽刺意味的是,vpermilps
与 vshufps dst, same,same, imm8
相比,除了内存源加载+shuffle 之外,带有立即数是多余的,并且出于代码大小的原因应避免使用。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
可以使用 Nearest、向 +-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]
进行加载+随机播放。
不幸的是,英特尔糟糕地设计了内存源 unpck 指令,因此它们需要一个 16 字节的内存源,而不仅仅是它们实际加载/使用的 8 个字节。有几种情况
【讨论】:
感谢您对选项的精彩总结。我编辑了我的问题,以显示我对你和 fuz 的信息做了什么。 非常感谢您的更新。我知道我需要避免记忆,你在我读完你的更新之前向我展示了如何(unf。我在此期间被叫去开会)。删除内存应该会更有效。 @RTC222:L1d 缓存和存储转发速度很快。存储/加载指令是单微指令。如果其他工作隐藏了 5 或 6 个周期的存储/重新加载延迟,那很好。在某些情况下,使用movhps [mem], xmm
存储高半部分以供以后重新加载而不是 ALU shuffle 实际上可能很好。以上是关于将浮点数从高 xmm 四字移动到低 xmm 四字的主要内容,如果未能解决你的问题,请参考以下文章
如何将两个打包的 64 位四字加载到 128 位 xmm 寄存器中