assembly 在汇编中,无分支代码是否应该使用互补的CMOV?

nx7onnlm  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(139)

众所周知,我们可以使用CMOV指令来编写无分支代码,但我想知道我是否在编写x = cond ? 1 : 2的等效代码,我是否更喜欢

CMOVE rax, 1    #1a
CMOVNE rax, 2   #1b

MOV rax, 1      #2a
CMOVNE rax, 2   #2b

理论上,第一个可以并行执行,而第二个由于数据依赖性而较慢,但我不确定实际情况如何。

pu3pd22g

pu3pd22g1#

第二个似乎更好。
首先,注意CMOVcc没有直接形式;第二个操作数也必须是寄存器(或内存),因此CMOVcc rax, rbx实际上有 * 三 * 个输入依赖性; raxrbxflagsrax是输入相关性,因为如果cc为假,则rax中的输出值必须等于它之前的值。因此,指令将始终暂停,直到所有三个输入就绪。
你可能会想象一个“条件依赖”,如果cc为真,指令就不需要在rax上停顿,但我不相信任何现有的机器能做到这一点。通常,条件移动被视为算术/逻辑指令,有点类似于adc rax, rbx:它会计算raxrbx和标志的函数,并将结果保存在rax中,你可以将这个函数想象成rax = (~mask & rax) | (mask & rbx).
(This是无分支代码的主要缺点之一:它总是要等待两个结果都准备好。一个分支可能看起来更糟糕,但是如果它被正确预测,那么它只等待真正需要的结果。Linus Torvalds wrote a famous rant about this.
所以第一个例子看起来更像是

mov rbx, 1
mov rcx, 2
cmp whatever
cmove rax, rbx
cmovne rax, rcx

(我知道我们应该使用32位寄存器来保存雷克斯前缀,但这只是一个例子。)
我们现在可以看到几个问题。

  • cmov必须等待rbxrcx准备好,但这可能不是问题;直接mov根本没有输入依赖性,因此它们可能在很久以前就已经乱序执行了。
  • 更严重的是,第二个cmov通过rax对第一个cmov的输出具有输入依赖性,因此这两个cmov实际上必须串行执行,而不是并行执行。
  • 更糟糕的是,第一个cmovrax的前一个值有一个 false 依赖关系。如果一些早期的代码对rax做了一些缓慢的事情,那么这段代码将暂停,直到另一段代码完成,即使早期代码在rax中留下的值与这个片段完全无关。

第二个备选方案如下所示:

mov rax, 1
mov rbx, 2
cmp whatever
cmovne rax, rbx

这样就避免了大部分问题。如前所述,在一个实施例中,直接的X1 M28 N1 X到X1 M29 N1 X和X1 M30 N1 X没有输入相关性并且可以预先完成。而且对rax的前一个值没有假依赖,因为mov rax, 1肯定会清除它。最后的好处是,这个版本少了一条指令。

相关问题