鉴于这个分支代码(如果条件预测良好,这将是有效的):
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 >= 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 输入之一的低字节。
请注意,vblendvps 和 vblendvpd 只关心每个 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 < 0,因为a-b 是通过截断数学来完成的。 jb 的条件是 CF == 1(而不是 MSB)。