2016-08-15 112 views
0

以下两个x86_64代码片段中的哪一个应该最快?或者根本没有区别?x86 - 指令级并行 - 指令的最佳顺序

; #1 
    bsf rax, rdi 
    mov rdx, -1 
    cmove rax, rdx 

; #2 
    mov rdx, -1 
    bsf rax, rdi 
    cmove rax, rdx 

(或者#1,更经济带寄存器的替代品。

; #1a 
    bsf rax, rdi 
    mov rdi, -1 
    cmove rax, rdi 

是的,我知道我应该标杆他们,但我没有这些工具,并且由于目前的长期致残疾病,我现在无法设置这些工具。

+0

像这样的问题通常只需编写代码并在多次迭代中计时就能得到最好的回答。 –

+2

@DavidHoelzer:我不同意:microbenchmarking很难,一个版本因为某些不相关的原因很容易会比另一个更快。当序列的延迟和吞吐量不同时,它也很容易出错。微型平台可能会测试吞吐量,而实际使用对延迟敏感。这不是一个很好的问题,但答案只是“阅读Agner Fog的东西”,而不是“自己试试”。如果你不知道我给出的答案,除了运气之外,你将无法写出一个好的微型基准。 –

回答

3

也看到标签维基性能环节,尤其是Agner Fog's microarch pdf and his Optimizing Assembly guide.


除非解码/前端效应开始发挥作用,他们是因为乱序执行的所有基本相同。 (否则这取决于周围的代码,并且是用于不同的微架构不同。)

它们都具有并行的相同量(两条链:独立mov(无输入)和bsf(一个输入),加从属CMOV) 。它足够小,以至于无序执行找到这种并行性是微不足道的。如果你关心按顺序的Atom,那么bsf和mov都可以配对。

任何区别将取决于周围的代码。

如果我不得不挑选,我可能会选择#1a,因为这样可以减少bsf盗取执行端口的机会。 mov r64, imm32-sign-extended可以在大多数CPU上的任何端口上运行,但bsf通常不能。将指示放在不会减少资源冲突的insns之前的关键路径上,至少在循环之外,来自先前迭代的非关键指令可以延迟关键路径。 (mov是关键路径中的一种,但它没有输入缩进,所以无序执行可以在它发布后的任何时刻运行,可能在产生bsf的输入的指令之前运行)。

我可能会使用#1a而不是#1来使该片段使用较少的寄存器用于未来验证。如果我有一个特定的用途来为某个寄存器启动一个新的依赖链,比如后面的指令有一个错误的依赖关系,并且这个寄存器的值取决于一个长的依赖关系链(或者一个可能缓存未命中的负载),我会使用#1。例如如果我想使用8位或16位寄存器或output register for popcnt

说起来,其中bsf也可能对Intel CPU有错误的依赖。如果输入值为0,则Intel CPU将保持目标不变。 (ISA说dest是未定义的,但这就是Core2实际所做的事情,这需要依赖目标寄存器以及源代码)。我怀疑这就是为什么lzcnt/tzcnt/popcnt对输出寄存器有依赖性。

说到错误的依赖关系:有趣的是,您可以通过执行or rdx, -1or r64, imm8),with a false dependency on the dst register.将寄存器设置为具有较少字节数的机器码。通常不好的主意,不要这样做。

+0

非常感谢,彼得,这非常有用的职位。 –

+0

我对Agner Fog的出色工作非常熟悉。他是英雄,强烈推荐, –