Assembly 基于标量整数条件的AVX向量寄存器的条件移动(cmov)?

Assembly 基于标量整数条件的AVX向量寄存器的条件移动(cmov)?,assembly,x86,avx,avx2,conditional-move,Assembly,X86,Avx,Avx2,Conditional Move,对于64位寄存器,如果满足条件cc,则有一条指令只将B写入A: ; Do rax <- rdx iff rcx == 0 test rcx, rcx cmove rax, rdx cmov是否有AVX等价物?如果没有,如何以无分支的方式实现此操作?虽然没有矢量化版本的cmov,但可以使用位掩码和 假设我们有两个256位向量value1和value2,它们位于相应的向量寄存器ymm1和ymm2中: align 32 value1: dq 1.0, 2.0, 3.0, 4.0 value2

对于64位寄存器,如果满足条件
cc
,则有一条指令只将
B
写入
A

; Do rax <- rdx iff rcx == 0
test rcx, rcx
cmove rax, rdx

cmov是否有AVX等价物?如果没有,如何以无分支的方式实现此操作?

虽然没有矢量化版本的
cmov
,但可以使用位掩码和


假设我们有两个256位向量
value1
value2
,它们位于相应的向量寄存器
ymm1
ymm2
中:

align 32
value1: dq 1.0, 2.0, 3.0, 4.0
value2: dq 5.0, 6.0, 7.0, 8.0
我们要比较两个寄存器
rcx
rdx

; Values to compare
mov rcx, 1
mov rdx, 2
如果它们相等,我们希望将
ymm2
复制到
ymm1
(并因此选择
value2
),否则我们希望保留
ymm1
,因此
value1

使用
cmov的等效(无效)表示法:

cmp rcx, rdx
cmove ymm1, ymm2  (invalid)

首先,我们将
rcx
rdx
加载到向量寄存器中,然后将它们复制到相应寄存器的所有64位块中(
描述了串联):

最后,我们使用
ymm0
中的掩码将
ymm2
转换为
ymm1

; If rcx == rdx: ymm1 <- ymm2
; If rcx != rdx: ymm1 <- ymm1
vpblendvb ymm1, ymm1, ymm2, ymm0
;如果rcx==rdx:ymm1给定此branchy代码(如果条件预测良好,则该代码将有效):

我们可以根据比较条件创建一个0/-1向量,并在其上进行混合,从而无分支地完成这项工作。与其他答案相比,有些优化:

  • 在XMM比较之后广播,因此不需要广播两个输入。保存一条指令,并进行仅比较XMM(在Zen1上保存uop)
  • 如果可以便宜地将整数输入减少为一个整数。所以,您只需要将一件事情从integer复制到XMM regs。标量异或可以在任何执行端口上运行,而
    vmovd/q xmm,reg
    只能在英特尔上的单个执行端口上运行:端口5,与
    vpq ymm,xmm等向量混洗所需的端口相同
除了总共节省1条指令外,它还使其中一些指令更便宜(同一执行端口的竞争更少,例如标量异或根本不是SIMD)和脱离关键路径(异或归零)。在循环中,你可以在循环外准备一个零向量

;; inputs: RCX, RDX.  YMM1, YMM2
;; output: YMM0

   xor      rcx, rdx        ; 0 or non-0.
   vmovq    xmm0, rcx
         vpxor xmm3, xmm3, xmm3   ; can be done any time, e.g. outside a loop
   vcmpeqq  xmm0, xmm0, xmm3      ; 0 if RCX!=RDX,  -1 if RCX==RDX

   vpbroadcastq ymm0, xmm0
   vpblendvb    ymm0, ymm1, ymm2, ymm0   ; ymm0 = (rcx==rdx) ? ymm2 : ymm1
销毁旧的RCX意味着您可能需要一个
mov
,但这仍然是值得的

类似于
rcx>=rdx
(无符号)的条件可以通过
cmp-rdx,rcx
/
sbb-rax,rax
实现0/-1整数(无需
vpcmpeqq
即可广播)

一种比情况更严重的症状更痛苦;您可能最终需要为
vpcmpgtq
使用2x
vmovq
,而不是
cmp
/
setg
/
vmovd
/
vpb
。特别是如果您没有方便的寄存器来
setg
以避免可能的错误依赖
setg al
/read EAX对于部分寄存器暂停不是问题:CPU足够新,可以有AVX2。(只有英特尔曾经这样做过,在Haswell中没有。)因此,不管怎样,您可以
setcc
进入
cmp
输入的低字节

请注意,
vblendvps
vblendvpd
只关心每个dword或qword元素的高位字节。如果您有两个正确的扩展整数,并且减去它们不会溢出,
c-d
将直接用作混合控件,只需广播即可。整数SIMD指令之间的FP混合,如
vpaddd
,在带有AVX2的英特尔CPU上(在AMD上可能类似),在输入和输出上有额外1个周期的旁路延迟,但保存的指令也会有延迟

对于无符号32位数字,在整数regs中可能已经将其零扩展到64位。在这种情况下,
sub-rcx,rdx
可以将rcx的MSB设置为与
cmp-ecx,edx
设置CF的方式相同。(请记住,for
jb
/
cmovb
CF==1

;;无符号32位比较,输入已经过零扩展
分rcx、rdx;设置MSB=(ecxvblendvpd ymm0、ymm1、ymm2、ymm0;ymm0=ECX这里没有这样的指令。您可以使用混合说明实现所需的效果;您只需要创建一个位掩码来指示所需的条件,而不是设置标志。将标志从rflags广播到向量中是很烦人的,希望在可以避免的上下文中使用它(例如,基于向量元素是否为零的掩码)@janw如果有更多的背景,可能会为您的具体案例提出解决方案。此外,您可以使用AVX2还是仅使用AVX?@janw您可以使用
vpbroadcastq
将块地址广播到YMM寄存器的所有元素中。还广播所需块的地址。然后,与
vpcmpeqq
进行比较,得到地址匹配的0和地址不匹配的-1。@fuz-Oh,这看起来是一个很好的方法,也是一个很好的解决方法。我会试试这个,谢谢!(我最后问了我的第一个XY问题,看起来…;))您可以在比较之后进行广播,并在复制到XMM注册表之前将整数值组合成0/non-0
xor rcx,rdx
/
vmovq xmm0,rcx
/
vpxor xmm3,xmm3,xmm3
(可提升)/
vcmpeqq xmm0,xmm0,xmm3
(-1如果rcx==rdx,否则0)/
VPQ ymm0,xmm0
/使用ymm0混合其他两个矢量。除了总共节省1条指令外,它还使其中一些指令更便宜(同一执行端口的竞争更少,例如标量异或根本不是SIMD)和脱离关键路径(异或归零)。在循环中,你可以在循环外准备一个零向量,这是一些很好的优化
vmovq xmm0, rcx          ; xmm0 <- 0 . rcx
vpbroadcastq ymm1, xmm0  ; ymm1 <- rcx . rcx . rcx . rcx
vmovq xmm0, rdx          ; xmm0 <- 0 . rdx
vpbroadcastq ymm2, xmm0  ; ymm2 <- rdx . rdx . rdx . rdx
; If rcx == rdx:  ymm0 <- ffffffffffffffff.ffffffffffffffff.ffffffffffffffff.ffffffffffffffff
; If rcx != rdx:  ymm0 <- 0000000000000000.0000000000000000.0000000000000000.0000000000000000
vpcmpeqq ymm0, ymm1, ymm2
; If rcx == rdx: ymm1 <- ymm2
; If rcx != rdx: ymm1 <- ymm1
vpblendvb ymm1, ymm1, ymm2, ymm0
    cmp rcx, rdx
    jne  .nocopy
     vmovdqa  ymm1, ymm2       ;; copy if RCX==RDX
.nocopy:
;; inputs: RCX, RDX.  YMM1, YMM2
;; output: YMM0

   xor      rcx, rdx        ; 0 or non-0.
   vmovq    xmm0, rcx
         vpxor xmm3, xmm3, xmm3   ; can be done any time, e.g. outside a loop
   vcmpeqq  xmm0, xmm0, xmm3      ; 0 if RCX!=RDX,  -1 if RCX==RDX

   vpbroadcastq ymm0, xmm0
   vpblendvb    ymm0, ymm1, ymm2, ymm0   ; ymm0 = (rcx==rdx) ? ymm2 : ymm1
;; unsigned 32-bit compare, with inputs already zero-extended
   sub   rcx, rdx               ; sets MSB = (ecx < edx)
   vmovq xmm0, rcx
   vpbroadcastq   ymm0, xmm0

   vblendvpd      ymm0, ymm1, ymm2, ymm0   ; ymm0 = ecx<edx ? ymm2 : ymm1