将浮点数从高 xmm 四字移动到低 xmm 四字
Move float from high xmm quadword to low xmm quadword
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
参见 Agner Fog's asm optimization guide,他关于 SIMD 的章节有一个 table 混洗指令,不同类型的数据移动,这会给你少量的指令来思考(或者在英特尔的手册中查找)如果你不记得他们到底做了什么),看看他们是否是你想要的。
将寄存器的高 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 中取出高 qwords 并按顺序将它们放入 xmm3 中。所以在目的地,高qword移动到低,然后从src的高qword被复制过来。 xmm3[0] = xmm3[1]; xmm3[1] = xmm4[1]
但是unpcklpd
没用,它长了1个字节并且和SSE1movlhps
做同样的事情。 (将低 qword 从 src 复制到目标的高 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 操作数,立即数的高位)。
随机播放控件可以多次读取同一个输入元素。
Interestingly, the NASM compiler will compile VUNPCKHPD with only two operands
NASM 允许您将 vaddps xmm0, xmm0, xmm1
之类的指令编写为 vaddps xmm0, xmm1
,当它与第一个源相同时省略单独的目标操作数。
I'm puzzled because these values are double precision, not single, but it works.
一切只是bits/bytes被复制到。除非您使用的是 FP 计算指令(例如 addpd
/ addps
),否则 "type" 无关紧要。 (您可以通过手册条目中是否存在 "SIMD Floating-Point Exceptions" 部分来判断它是否关心作为 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 源 "integer" 随机播放,只有 palignr
和 punpck
指令。在 AVX 之前,没有 FP copy-and-shuffle 指令。 (具有讽刺意味的是,与 vshufps dst, same,same, imm8
相比,带立即数的 vpermilps
是多余的,除了内存源加载+洗牌,并且由于代码大小的原因应该避免。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
这是一种高效的洗牌,但不幸的是,它在第一轮的输出和第二轮的输入之间产生了错误的依赖关系。所以这两个调用不能并行工作。相反,在第一次调用之前复制时随机播放,最好是进入一个你知道已经 "dead" 一段时间的寄存器,或者是 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 (t运行阳离子)。
movsd [scratch_register+8],xmm0 ; write low qword to memory
movupd xmm11,[scratch_register]
永远不要这样做,narrow store + wide reload 是有保证的存储转发停顿。 (约 10 个周期的额外延迟)。
使用 16 字节对齐的存储位置(例如在堆栈上 [rsp+8]
或其他位置),并且
unpckhpd xmm0, [scratch_register]
加载+随机播放.
不幸的是,英特尔设计的内存源 unpck 指令很糟糕,因此它们需要 16 字节的内存源,而不仅仅是它们实际上的 8 字节 load/use。有几种情况
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
参见 Agner Fog's asm optimization guide,他关于 SIMD 的章节有一个 table 混洗指令,不同类型的数据移动,这会给你少量的指令来思考(或者在英特尔的手册中查找)如果你不记得他们到底做了什么),看看他们是否是你想要的。
将寄存器的高 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 中取出高 qwords 并按顺序将它们放入 xmm3 中。所以在目的地,高qword移动到低,然后从src的高qword被复制过来。xmm3[0] = xmm3[1]; xmm3[1] = xmm4[1]
但是unpcklpd
没用,它长了1个字节并且和SSE1movlhps
做同样的事情。 (将低 qword 从 src 复制到目标的高 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 操作数,立即数的高位)。
随机播放控件可以多次读取同一个输入元素。
Interestingly, the NASM compiler will compile VUNPCKHPD with only two operands
NASM 允许您将 vaddps xmm0, xmm0, xmm1
之类的指令编写为 vaddps xmm0, xmm1
,当它与第一个源相同时省略单独的目标操作数。
I'm puzzled because these values are double precision, not single, but it works.
一切只是bits/bytes被复制到。除非您使用的是 FP 计算指令(例如 addpd
/ addps
),否则 "type" 无关紧要。 (您可以通过手册条目中是否存在 "SIMD Floating-Point Exceptions" 部分来判断它是否关心作为 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 源 "integer" 随机播放,只有 palignr
和 punpck
指令。在 AVX 之前,没有 FP copy-and-shuffle 指令。 (具有讽刺意味的是,与 vshufps dst, same,same, imm8
相比,带立即数的 vpermilps
是多余的,除了内存源加载+洗牌,并且由于代码大小的原因应该避免。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
这是一种高效的洗牌,但不幸的是,它在第一轮的输出和第二轮的输入之间产生了错误的依赖关系。所以这两个调用不能并行工作。相反,在第一次调用之前复制时随机播放,最好是进入一个你知道已经 "dead" 一段时间的寄存器,或者是 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 (t运行阳离子)。
movsd [scratch_register+8],xmm0 ; write low qword to memory
movupd xmm11,[scratch_register]
永远不要这样做,narrow store + wide reload 是有保证的存储转发停顿。 (约 10 个周期的额外延迟)。
使用 16 字节对齐的存储位置(例如在堆栈上 [rsp+8]
或其他位置),并且
unpckhpd xmm0, [scratch_register]
加载+随机播放.
不幸的是,英特尔设计的内存源 unpck 指令很糟糕,因此它们需要 16 字节的内存源,而不仅仅是它们实际上的 8 字节 load/use。有几种情况