Dan*_*iel 6 x86 assembly micro-optimization conditional-move branchless
众所周知,我们可以使用 CMOV 指令来编写无分支代码,但我想知道我是否正在编写等效的指令x = cond ? 1 : 2,我应该更喜欢
CMOVE rax, 1 #1a
CMOVNE rax, 2 #1b
Run Code Online (Sandbox Code Playgroud)
或者
MOV rax, 1 #2a
CMOVNE rax, 2 #2b
Run Code Online (Sandbox Code Playgroud)
理论上,第一个可以几乎并行执行,而第二个由于数据依赖性而速度较慢。但我不确定现实情况如何。
Nat*_*dge 10
第二个似乎更好。
首先,请注意CMOVcc没有直接形式;第二个操作数也必须是寄存器(或内存)。因此CMOVcc rax, rbx实际上具有三个输入依赖项;rax,rbx, 和flags。 rax是输入依赖项,因为如果cc为 false,则 in 的输出值rax必须等于之前的值。因此,指令将始终停止,直到所有三个输入都准备好为止。
rax您可能会想象一种“条件依赖”,其中如果为真,指令不需要停止cc,但我不相信任何当前现有的机器实际上可以做到这一点。一般来说,条件移动被视为算术/逻辑指令,有点类似于adc rax, rbx:它计算 的某个函数rax和rbx标志,并将结果保留在 中rax。您可以将该函数视为类似rax = (~mask & rax) | (mask & rbx).
(这是无分支代码的主要缺点之一:它总是必须等待两个结果都准备好。分支可能看起来更糟糕,但如果它被正确预测,那么它只等待实际需要的结果 。Linus托瓦尔兹对此写了一篇著名的咆哮。)
所以第一个例子看起来更完整
mov rbx, 1
mov rcx, 2
cmp whatever
cmove rax, rbx
cmovne rax, rcx
Run Code Online (Sandbox Code Playgroud)
(我知道我们应该使用 32 位寄存器来保存 REX 前缀,但这只是一个示例。)
我们现在可以看到几个问题。
scmov必须等待rbx并rcx做好准备,但这可能不是问题;立即mov数根本没有输入依赖性,因此它们很可能早就乱序执行了。
更严重的是,第二个cmov输入依赖于第一个输出,通过rax。所以这两个cmov实际上必须串行执行,而不是并行执行。
更糟糕的是,第一个错误地cmov依赖于先前的值was。如果某些较早的代码正在执行缓慢的操作,则此代码将停止,直到其他代码完成,即使较早的代码留下的值应该与此代码段完全无关。raxraxrax
第二种选择如下:
mov rax, 1
mov rbx, 2
cmp whatever
cmovne rax, rbx
Run Code Online (Sandbox Code Playgroud)
这可以避免大部分问题。现在唯一真正的输入依赖是由 生成的标志cmp(因此无论输入是什么cmp)。和以前一样,立即数movs torax和rbx没有输入依赖性,可以提前完成。并且不存在对先前值的错误依赖,rax因为mov rax, 1最终将其消除。作为最后的奖励,这个版本缩短了一条指令。