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

问题描述

对于 64 位寄存器,有 CMOVcc A,B 指令,只有在满足条件 B 时才将 A 写入 cc

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

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

解决方法

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


假设我们有两个 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),否则我们想保留ymm1,因此{{1} }.

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

cmov

首先,我们将 cmp rcx,rdx cmove ymm1,ymm2 (invalid) rcx 加载到向量寄存器并 broadcast 它们,因此它们被复制到相应寄存器的所有 64 位块(rdx 描述串联):

.

然后,我们使用 vpcmpeqq 生成掩码:

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

最后,我们blend ; If rcx == rdx: ymm0 <- ffffffffffffffff.ffffffffffffffff.ffffffffffffffff.ffffffffffffffff ; If rcx != rdx: ymm0 <- 0000000000000000.0000000000000000.0000000000000000.0000000000000000 vpcmpeqq ymm0,ymm1,ymm2 进入ymm2,使用ymm1 中的掩码:

ymm0

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

,

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

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

我们可以通过基于比较条件创建一个 0 / -1 向量并在其上混合来实现无分支。一些优化与其他答案:

  • 广播 after XMM 比较,因此您不需要广播两个输入。保存一条指令,并只进行 XMM 比较(在 Zen1 上节省一个 uop)。
  • 如果可以便宜地将整数输入减少到一个整数。所以你只需要从整数复制一件事到 XMM regs。标量异或可以在任何执行端口上运行,而 vmovd/q xmm,reg 只能在 Intel 上的单个执行端口上运行:端口 5,与 vpbroadcastq 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,ymm2,ymm0   ; ymm0 = (rcx==rdx) ? ymm2 : ymm1

销毁旧的 RCX 意味着您可能需要一个 mov,但这仍然值得。

rcx >= rdx(无符号)这样的条件可以用 cmp rdx,rcx / sbb rax,rax 来实现一个 0 / -1 整数(你可以广播没有需要vpcmpeqq)。

签名大于条件更痛苦;您可能最终需要 2x vmovq 来表示 vpcmpgtq,而不是 cmp/setg/vmovd / vpbroadcastb。特别是如果您没有方便的寄存器来 setg 进入以避免可能的错误依赖。 setg al / read EAX 不是部分寄存器停顿的问题:新的 CPU 足以拥有 AVX2 don't rename AL separately from the rest of RAX。 (只有英特尔这样做过,而 Haswell 没有这样做。)所以无论如何,您可以setcc进入您的 cmp 输入之一的低字节。 >

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

对于无符号的 32 位数字,您很可能已经在整数 regs 中将它们零扩展到 64 位。在这种情况下,sub rcx,rdx 可以将 RCX 的 MSB 设置为与 cmp ecx,edx 设置 CF 的方式相同。 (请记住,jb / cmovbFLAGS conditionCF == 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,ymm0   ; ymm0 = ecx<edx ? ymm2 : ymm1

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

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