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

Posted

技术标签:

【中文标题】基于标量整数条件的AVX向量寄存器的条件移动(cmov)?【英文标题】:Conditional move (cmov) for AVX vector registers based on scalar integer condition? 【发布时间】:2021-02-02 17:23:58 【问题描述】:

对于 64 位寄存器,有 CMOVcc A, 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)

cmov 是否有 AVX 等效项?如果没有,我怎样才能以无分支的方式实现这个操作?

【问题讨论】:

没有这样的说明。您可以使用混合指令来达到预期的效果;您只需要创建一个位掩码来指示所需条件,而不是设置标志。 将标志从 rflags 广播到向量中很烦人,希望这可以在可以避免的情况下使用(例如基于向量的元素是否为零的掩码) @janw 有了更多上下文,可能会为您的特定情况提出解决方案。另外,您可以使用 AVX2 还是只使用 AVX? @janw 您可以使用vpbroadcastq 将块地址广播到 YMM 寄存器的所有元素中。还广播所需块的地址。然后,与vpcmpeqq 比较,得到地址匹配的 0 和不匹配的 -1。 @fuz 哦,这看起来是一个不错的方法,也是一个不错的解决方法。我会试试这个,谢谢! (我终于要问我的第一个 XY 问题,因为它看起来......;)) 【参考方案1】:

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

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

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

在 XMM 比较之后广播,因此您不需要广播两个输入。保存一条指令,并且只比较 XMM(在 Zen1 上保存一个 uop)。 如果您可以廉价地做到这一点,请将整数输入减少到一个整数。所以你只需要将一件事从整数复制到 XMM regs。标量 xor 可以在任何执行端口上运行,而 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, ymm1, ymm2, ymm0   ; ymm0 = (rcx==rdx) ? ymm2 : ymm1

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

rcx &gt;= rdx(无符号)之类的条件可以使用cmp rdx, rcx / sbb rax,rax 来实现一个 0 / -1 整数(您可以在广播不需要vpcmpeqq)。

有符号大于条件更痛苦;你最终可能会想要 2x vmovq 换成 vpcmpgtq,而不是 cmp/setg/vmovd / vpbroadcastb。特别是如果您没有方便的注册到setg 以避免可能的错误依赖。 setg al / 读取 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 位数字,您很可能已经将它们零扩展为 64 位整数寄存器。在这种情况下,sub rcx, rdx 可以像cmp ecx, edx 设置 CF 一样设置 RCX 的 MSB。 (请记住,jb / cmovb 的 FLAGS condition 是 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 &lt; 0,因为a-b 是通过截断数学来完成的。 jb 的条件是 CF == 1(而不是 MSB)。

【讨论】:

嗯,这是一些不错的优化,还有一些有趣的见解!老实说,我不知道我是怎么错过了我可以直接在 64 位寄存器中进行整数比较并在顶部保存广播指令这一事实。 @janw: xor 在 x86 上通常不需要获取 0/non-0,因为 ISA 有 FLAGS;不过,它是 MIPS / RISC-V 上 (int)(x!=y) 的一部分。 (How to do less than or equal in Assembly Language(MIPS)?)。事后看来,一些好的优化是显而易见的,在其他情况下,我当然忽略了我自己的那部分。 :P【参考方案2】:

虽然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 并因此保留value1

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

cmp rcx, rdx
cmove ymm1, ymm2  (invalid)

首先,我们将rcxrdx 加载到向量寄存器和broadcast 它们中,因此它们被复制到相应寄存器的所有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

最后,我们将blendymm2变成ymm1,使用ymm0中的掩码:

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

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

【讨论】:

你可以广播after比较,并在复制到XMM reg之前将整数值组合成0/非0。 xor rcx, rdx/vmovq xmm0, rcx/vpxor xmm3, xmm3, xmm3(可提升)/vcmpeqq xmm0, xmm0, xmm3(如果 RCX==RDX,则为 -1,否则为 0)/vpbroadcastq ymm0, xmm0/使用 ymm0 混合其他两个向量。除了节省 1 条总指令外,它还使其中一些指令更便宜(同一个执行端口的竞争更少,例如标量异或根本不是 SIMD)并脱离关键路径(异或归零)。并且在循环中,您可以在循环外准备一个归零向量。

以上是关于基于标量整数条件的AVX向量寄存器的条件移动(cmov)?的主要内容,如果未能解决你的问题,请参考以下文章

SSE 向量重新对齐?

用 AVX2 有条件地选择一个常数值

随机播放 AVX 寄存器中的元素

将 256 位 AVX 向量存储为无符号长整数的最佳方法

将整数数组加载到 AVX 寄存器中

向量指令和标量指令