Assembly 将浮点从高xmm四字移到低xmm四字

Assembly 将浮点从高xmm四字移到低xmm四字,assembly,x86-64,nasm,sse,avx,Assembly,X86 64,Nasm,Sse,Avx,MOVHPD将xmm寄存器的高位四字提取到内存中 PEXTRQ提取xmm寄存器的高位四字,并将其仅放入整数寄存器 SHUFPD洗牌 VPSLLDQ使高位四字归零 是否有指令将浮点值从xmm寄存器的高位四字移动到同一xmm寄存器或另一xmm寄存器的低位四字?还是我总是要通过内存添加额外的周期 更新: 根据@fuz和@Peter Cordes在下面的评论,以下是我所做的。这将分别为xmm0的低位和高位四字调用舍入函数;由于特殊的舍入参数,必须为每个qword单独调用该函数,因此它不能是SIMD指令。

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
看,他关于SIMD的一章有一个洗牌指令表,不同类型的数据移动,如果你不记得它们的确切功能,看看它们是否是你想要的,它会给你提供少量的指令供你思考或查阅英特尔的手册

向两个元素广播寄存器的高Q字最便宜的方法是movhlps xmm0,xmm0。或者对于整数数据,如果您的代码可能在Nehalem上运行,请使用punpckhqdq xmm0、xmm0来避免FPvec int旁路延迟

如果没有AVX,movhlps是不错的,因为它的洗牌效果与AVX稍有不同

movhlps xmm3,xmm4做xmm3[0]=xmm4[1];,保持xmm3[1]不变。 unpckhpd xmm3,xmm4从xmm3和xmm4中获取高qwords,并按顺序将它们放入xmm3中。因此在目的地中,高qword移动到低qword,然后从src复制高qword。xmm3[0]=xmm3[1];xmm3[1]=xmm4[1] 但是unpcklpd是无用的,它比SSE1 movlhps长1字节,并且做的事情与SSE1 movlhps相同。将src中的低qword复制到目标的高qword,使目标的低qword保持不变。与movapd相同,始终使用movaps

另外,关于:代码大小:使用xmm8..15需要一个REX前缀,因此请选择寄存器分配,在尽可能少的指令中使用xmm8..15,或者在已经需要REX前缀的指令中使用xmm8..15,例如r8..15中的指针。代码大小通常不是什么大问题,但所有其他大小相同的代码通常都是最好的。较小的指令通常更好地打包到uop缓存中

使用AVX,您可以将vunpckhpd与源操作数的任意顺序一起使用,第一个src的高Q字将转到目标的低Q字。VMOVHLP没有代码大小优势或其他性能优势,它们都可以使用2字节VEX前缀,最小指令大小为4字节

e、 vunpckhpd xmm0,xmm1,xmm0类似于vmovhlps xmm0,xmm0,xmm1

你可以用它来解决你要解决的问题。这是对代码大小的浪费,因为它需要立即执行,但显然您没有意识到可以使用shufpd xmm0、xmm0、0b11按以下顺序执行:

xmm0[1]第一个src操作数的低位Q字,立即数的低位 xmm0[1]秒src操作数的高位Q字,立即数的高位。 shuffle控件可以多次读取同一输入元素

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

NASM允许您将vaddps xmm0、xmm0、xmm1等指令写入为vaddps xmm0、xmm1,当其与第一个源相同时,可以省略单独的目标操作数

我很困惑,因为这些值是双精度的,不是单精度的,但它是有效的

所有的东西都是要复制的位/字节。除非您使用FP计算指令,例如addpd/addps,否则类型无关紧要。您可以通过手动输入中是否存在SIMD浮点异常部分来判断它是否关心位作为FP位模式的含义。e、 g.addps: . 但这并不奇怪。唯一关心这个问题的指令是出于非常明显的原因,比如进行FP计算或类型转换,而不仅仅是复制数据

没有真正的CPU关心PS与PD指令的性能,但有些CPU关心vec int与vec FP,因此不幸的是,使用pshufd复制和洗牌FP数据并不总是一个胜利。或者将shufps用作2源整数洗牌

不幸的是,在AVX512之前,没有通用的2源整数洗牌,只有palignr和punpck指令。在AVX之前,没有FP复制和洗牌指令。具有讽刺意味的是,带立即数的vpermilps与vshufps dst、same、same、imm8相比是冗余的,除了内存源加载+洗牌之外,并且出于代码大小的原因应该避免

这是一种高效的洗牌,但不幸的是,它在第一轮的输出和第二轮的输入之间创建了错误的依赖关系。所以t wo调用不能并行工作。取而代之的是,在第一次调用之前进行复制时,最好是将其洗牌到一个寄存器中,您知道该寄存器已经死了一段时间,或者是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
除非您的手写循环函数无法触及的寄存器太少,否则您不需要特别的内存,也不会更高效

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

另一个选项,特别是如果您的输入位于不同的寄存器中,则首先将高半部四舍五入,然后可以使用movlhps将高半部放回xmm0

顺便说一句,如果您有SSE4.1,roundpd可以使用最近的、朝向+-Inf ceil/floor或朝向0截断的整数进行舍入

永远不要这样做,窄店+宽店换货是一个保证店转发暂停~额外延迟10个周期

使用16字节对齐的存储位置,例如在堆栈上[rsp+8]或其他位置,以及 取消打包HPD xmm0,[暂存器]以加载+洗牌

不幸的是,英特尔设计的内存源解压指令很糟糕,因此它们需要16字节的内存源,而不仅仅是它们实际加载/使用的8字节。有几种情况下,

参见,他关于SIMD的章节中有一个混洗指令表,其中包含不同类型的数据移动,如果您记不清这些指令的具体功能,并且不知道它们是否是您想要的,则可以提供少量指令供您思考或查阅英特尔手册

向两个元素广播寄存器的高Q字最便宜的方法是movhlps xmm0,xmm0。或者对于整数数据,如果您的代码可能在Nehalem上运行,请使用punpckhqdq xmm0、xmm0来避免FPvec int旁路延迟

如果没有AVX,movhlps是不错的,因为它的洗牌效果与AVX稍有不同

movhlps xmm3,xmm4做xmm3[0]=xmm4[1];,保持xmm3[1]不变。 unpckhpd xmm3,xmm4从xmm3和xmm4中获取高qwords,并按顺序将它们放入xmm3中。因此在目的地中,高qword移动到低qword,然后从src复制高qword。xmm3[0]=xmm3[1];xmm3[1]=xmm4[1] 但是unpcklpd是无用的,它比SSE1 movlhps长1字节,并且做的事情与SSE1 movlhps相同。将src中的低qword复制到目标的高qword,使目标的低qword保持不变。与movapd相同,始终使用movaps

另外,关于:代码大小:使用xmm8..15需要一个REX前缀,因此请选择寄存器分配,在尽可能少的指令中使用xmm8..15,或者在已经需要REX前缀的指令中使用xmm8..15,例如r8..15中的指针。代码大小通常不是什么大问题,但所有其他大小相同的代码通常都是最好的。较小的指令通常更好地打包到uop缓存中

使用AVX,您可以将vunpckhpd与源操作数的任意顺序一起使用,第一个src的高Q字将转到目标的低Q字。VMOVHLP没有代码大小优势或其他性能优势,它们都可以使用2字节VEX前缀,最小指令大小为4字节

e、 vunpckhpd xmm0,xmm1,xmm0类似于vmovhlps xmm0,xmm0,xmm1

你可以用它来解决你要解决的问题。这是对代码大小的浪费,因为它需要立即执行,但显然您没有意识到可以使用shufpd xmm0、xmm0、0b11按以下顺序执行:

xmm0[1]第一个src操作数的低位Q字,立即数的低位 xmm0[1]秒src操作数的高位Q字,立即数的高位。 shuffle控件可以多次读取同一输入元素

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

NASM允许您将vaddps xmm0、xmm0、xmm1等指令写入为vaddps xmm0、xmm1,当其与第一个源相同时,可以省略单独的目标操作数

我很困惑,因为这些值是双精度的,不是单精度的,但它是有效的

所有的东西都是要复制的位/字节。除非您使用FP计算指令,例如addpd/addps,否则类型无关紧要。您可以通过手动输入中是否存在SIMD浮点异常部分来判断它是否关心位作为FP位模式的含义。e、 g.addps: . 但这并不奇怪。唯一关心这个问题的指令是出于非常明显的原因,比如进行FP计算或类型转换,而不仅仅是复制数据

没有真正的CPU关心PS与PD指令的性能,但有些CPU关心vec int与vec FP,因此不幸的是,使用pshufd复制和洗牌FP数据并不总是一个胜利。或者将shufps用作2源整数洗牌

不幸的是,在AVX512之前,没有通用的2源整数洗牌,只有palignr和punpck指令。在AVX之前,没有FP复制和洗牌指令。 具有讽刺意味的是,带立即数的vpermilps与vshufps dst、same、same、imm8相比是冗余的,除了内存源加载+洗牌之外,并且出于代码大小的原因应该避免

这是一种高效的洗牌,但不幸的是,它在第一轮的输出和第二轮的输入之间创建了错误的依赖关系。所以这两个调用不能并行工作。取而代之的是,在第一次调用之前进行复制时,最好是将其洗牌到一个寄存器中,您知道该寄存器已经死了一段时间,或者是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
除非您的手写循环函数无法触及的寄存器太少,否则您不需要特别的内存,也不会更高效

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

另一个选项,特别是如果您的输入位于不同的寄存器中,则首先将高半部四舍五入,然后可以使用movlhps将高半部放回xmm0

顺便说一句,如果您有SSE4.1,roundpd可以使用最近的、朝向+-Inf ceil/floor或朝向0截断的整数进行舍入

永远不要这样做,窄店+宽店换货是一个保证店转发暂停~额外延迟10个周期

使用16字节对齐的存储位置,例如在堆栈上[rsp+8]或其他位置,以及 取消打包HPD xmm0,[暂存器]以加载+洗牌


不幸的是,英特尔设计的内存源解压指令很糟糕,因此它们需要16字节的内存源,而不仅仅是它们实际加载/使用的8字节。有几种情况下,

我认为unpckhpd应该起作用。谢谢-我现在就来检查一下。你确定这段代码是正确的吗?vunpckhpd有三个操作数,所以看起来很奇怪。而且,这看起来绝对不正确。unpckhpd和vunpckhpd应该做您期望他们做的事情。也许您想要vunpcklpd或unpcklpd?您可以使用movhlps。请理解这些指令只是移动字节。它们代表什么并不重要。我认为unpckhpd应该起作用。谢谢-我现在就来检查一下。你确定这段代码是正确的吗?vunpckhpd有三个操作数,所以看起来很奇怪。而且,这看起来绝对不正确。unpckhpd和vunpckhpd应该做您期望他们做的事情。也许您想要vunpcklpd或unpcklpd?您可以使用movhlps。请理解这些指令只是移动字节。它们代表什么并不重要。感谢您对选项的精彩总结。我编辑了我的问题来展示我对你和fuz的信息做了什么。非常感谢更新。我知道我需要避免记忆,在我读完你的更新unf之前,你向我展示了如何避免记忆。在此期间,我被叫去开会。删除内存后应该更有效。@RTC222:L1d缓存和存储转发速度更快。存储/装载说明为单uop。如果其他工作隐藏了5或6个周期的存储/重新加载延迟,则没有问题。在某些情况下,使用movhps[mem],xmm存储一个高半音以备以后重新加载,而不是ALU shuffle,实际上是很好的选择。感谢您对这些选项的精彩总结。我编辑了我的问题来展示我对你和fuz的信息做了什么。非常感谢更新。我知道我需要避免记忆,在我读完你的更新unf之前,你向我展示了如何避免记忆。在此期间,我被叫去开会。删除内存后应该更有效。@RTC222:L1d缓存和存储转发速度更快。存储/装载说明为单uop。如果其他工作隐藏了5或6个周期的存储/重新加载延迟,则没有问题。在某些情况下,使用movhps[mem],xmm存储一个高半音以备以后重新加载,而不是ALU shuffle实际上是很好的。
movsd [scratch_register+8],xmm0 ; write low qword to memory
movupd xmm11,[scratch_register]