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



对于64位寄存器,存在CMOVccA, B指令,仅在cc满足条件时才将B写入A:

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

然而,我找不到AVX的任何等效的东西。我仍然想根据RFLAGS的值移动,只是使用更大的操作数:

; Do ymm1 <- ymm2 iff rcx == 0
test rcx, rcx
cmove ymm1, ymm2  (invalid)

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

给出这个分支代码(如果条件预测良好,它将是有效的):

cmp rcx, rdx
jne  .nocopy
vmovdqa  ymm1, ymm2       ;; copy if RCX==RDX
.nocopy:

我们可以通过基于比较条件创建一个0/-1向量并在其上进行混合来无分支地完成它。一些优化vs.另一个答案:

  • 在 XMM比较之后广播,因此您不需要广播两个输入。保存一条指令,并只进行XMM比较(在Zen1上保存一个上限)。
  • 如果成本低,可以将整数输入减少为一个整数。因此,您只需要将一个东西从整数复制到XMM regs。标量xor可以在任何执行端口上运行,而vmovd/q xmm, reg只能在Intel端口5上的单个执行端口上运行,与vpbroadcastq ymm, xmm等矢量shuffle所需的端口相同。

除了节省1条总指令外,它还使其中一些更便宜(相同执行端口的竞争更少,例如标量xor根本不是SIMD)并且远离关键路径(xor为零)。在循环中,可以在循环外准备一个归零向量。

;; 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(unsigned)这样的条件可以用cmp rdx, rcx/sbb rax,rax来实现一个0/-1整数(你可以广播而不需要vpcmpeqq

)。signed-greater-than条件更令人痛苦;你可能最终想要2xvmovqvpcmpgtq,而不是cmp/setg/vmovd/vpbroadcastb。特别是如果您没有一个方便的寄存器到setg以避免可能的错误依赖。setg al/read EAX对于部分寄存器延迟来说不是问题:具有AVX2的新cpu不会将AL与RAX的其余部分分开重命名。(只有英特尔这样做过,但在Haswell没有。)因此,无论如何,可以setcc转换为cmp输入的低字节。

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

对于无符号32位数字,您可能已经将它们在整数regs中从零扩展到64位。在这种情况下,sub rcx, rdx可以设置RCX的MSB与cmp ecx, edx设置CF的方式相同(并且记住jb/cmovb的FLAGS条件是CF == 1)

;; 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

但是如果你的输入已经是64位的,并且你不知道它们的范围是有限的,您需要一个65位的结果才能完全捕获64位的减法结果。

这就是为什么jl的条件是SF != OF,而不仅仅是a-b < 0,因为a-b是用截断数学完成的。jb的条件是CF == 1(而不是MSB)。

虽然没有矢量化版本的cmov,但可以使用位掩码和混合来实现等效的功能。


假设我们有两个256位向量value1value2,它们分别位于相应的向量寄存器ymm1ymm2中:

align 32
value1: dq 1.0, 2.0, 3.0, 4.0
value2: dq 5.0, 6.0, 7.0, 8.0
; Operands for our conditional move
vmovdqa ymm1, [rel value1]
vmovdqa ymm2, [rel value2]

我们想比较两个寄存器rcxrdx:

; Values to compare
mov rcx, 1
mov rdx, 2

如果它们相等,我们希望将ymm2复制到ymm1中(从而选择value2),否则我们希望保留ymm1value1

等效(无效)符号使用cmov:

cmp rcx, rdx
cmove ymm1, ymm2  (invalid)

首先,我们将rcxrdx加载到矢量寄存器中并广播它们,因此它们被复制到各自寄存器的所有64位块中(.描述了一个连接):

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
然后,我们使用vpcmpeqq: 生成一个掩码
; If rcx == rdx:  ymm0 <- ffffffffffffffff.ffffffffffffffff.ffffffffffffffff.ffffffffffffffff
; If rcx != rdx:  ymm0 <- 0000000000000000.0000000000000000.0000000000000000.0000000000000000
vpcmpeqq ymm0, ymm1, ymm2

最后,我们使用ymm0中的掩码将ymm2混合到ymm1中:

; If rcx == rdx: ymm1 <- ymm2
; If rcx != rdx: ymm1 <- ymm1
vpblendvb ymm1, ymm1, ymm2, ymm0

感谢@fuz,他在评论中概述了这种方法!

相关内容

  • 没有找到相关文章

最新更新