对于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
,但这仍然是值得的。
像 )。signed-greater-than条件更令人痛苦;你可能最终想要2x 注意 对于无符号32位数字,您可能已经将它们在整数regs中从零扩展到64位。在这种情况下, 但是如果你的输入已经是64位的,并且你不知道它们的范围是有限的,您需要一个65位的结果才能完全捕获64位的减法结果。 这就是为什么rcx >= rdx
(unsigned)这样的条件可以用cmp rdx, rcx
/sbb rax,rax
来实现一个0/-1整数(你可以广播而不需要vpcmpeqq
vmovq
的vpcmpgtq
,而不是cmp
/setg
/vmovd
/vpbroadcastb
。特别是如果您没有一个方便的寄存器到setg
以避免可能的错误依赖。setg al
/read EAX对于部分寄存器延迟来说不是问题:具有AVX2的新cpu不会将AL与RAX的其余部分分开重命名。(只有英特尔这样做过,但在Haswell没有。)因此,无论如何,可以将setcc
转换为cmp
输入的低字节。vblendvps
和vblendvpd
只关心每个dword或qword元素的高字节。如果你有两个正确的符号扩展的整数,和减去它们不会溢出,c - d
将直接用作你的混合控制,只是广播。整数SIMD指令(如vpaddd
)之间的FP混合在输入和输出上有额外的1个周期的旁路延迟,在带有AVX2的英特尔cpu上(可能在AMD上类似),但您保存的指令也会有延迟。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
jl
的条件是SF != OF
,而不仅仅是a-b < 0
,因为a-b
是用截断数学完成的。jb
的条件是CF == 1
(而不是MSB)。
虽然没有矢量化版本的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
; Operands for our conditional move
vmovdqa ymm1, [rel value1]
vmovdqa ymm2, [rel value2]
我们想比较两个寄存器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位块中(.
描述了一个连接):
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,他在评论中概述了这种方法!