x86 - 指令级并行性 - 最佳指令顺序

x86 - Instruction-level parallelism - optimal order of instructions

下面哪两个 x86_64 代码片段应该是最快的?还是完全没有区别?

; #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

)

是的,我知道我应该对它们进行基准测试,但我没有工具,而且由于目前长期患有致残疾病,我现在无法进行设置。

另请参阅 tag wiki, especially Agner Fog's microarch pdf and his Optimizing Assembly guide.

中的性能链接

除非解码/前端效果发挥作用,它们基本上都是平等的,因为乱序执行。 (否则它取决于周围的代码,并且对于不同的微架构是不同的。)

它们都具有相同数量的并行性(两条链:独立的 mov(无输入)和 bsf(一个输入),加上依赖的 cmov)。它足够小,对于乱序执行来说找到这种并行性是微不足道的。如果您关心有序 Atom,那么 bsf 和 mov 都可以配对。

任何差异都取决于周围的代码。

如果非要我选的话,我可能会选择 #1a,因为这样可以减少 movbsf 窃取执行端口的机会。 mov r64, imm32-sign-extended 可以 运行 在大多数 CPU 的任何端口上,但 bsf 通常不能。将指令放在不会减少资源冲突的 insn 之前的关键路径上,至少在循环之外,在该循环中,来自先前迭代的非关键指令可能会延迟关键路径。 (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, -1 (or r64, imm8), 将寄存器设置为机器码字节数较少的全一。通常是个坏主意,不要这样做。