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
)
是的,我知道我应该对它们进行基准测试,但我没有工具,而且由于目前长期患有致残疾病,我现在无法进行设置。
另请参阅 x86 tag wiki, especially Agner Fog's microarch pdf and his Optimizing Assembly guide.
中的性能链接
除非解码/前端效果发挥作用,它们基本上都是平等的,因为乱序执行。 (否则它取决于周围的代码,并且对于不同的微架构是不同的。)
它们都具有相同数量的并行性(两条链:独立的 mov
(无输入)和 bsf
(一个输入),加上依赖的 cmov)。它足够小,对于乱序执行来说找到这种并行性是微不足道的。如果您关心有序 Atom,那么 bsf 和 mov 都可以配对。
任何差异都取决于周围的代码。
如果非要我选的话,我可能会选择 #1a
,因为这样可以减少 mov
从 bsf
窃取执行端口的机会。 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
), 将寄存器设置为机器码字节数较少的全一。通常是个坏主意,不要这样做。
下面哪两个 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
)
是的,我知道我应该对它们进行基准测试,但我没有工具,而且由于目前长期患有致残疾病,我现在无法进行设置。
另请参阅 x86 tag wiki, especially Agner Fog's microarch pdf and his Optimizing Assembly guide.
中的性能链接除非解码/前端效果发挥作用,它们基本上都是平等的,因为乱序执行。 (否则它取决于周围的代码,并且对于不同的微架构是不同的。)
它们都具有相同数量的并行性(两条链:独立的 mov
(无输入)和 bsf
(一个输入),加上依赖的 cmov)。它足够小,对于乱序执行来说找到这种并行性是微不足道的。如果您关心有序 Atom,那么 bsf 和 mov 都可以配对。
任何差异都取决于周围的代码。
如果非要我选的话,我可能会选择 #1a
,因为这样可以减少 mov
从 bsf
窃取执行端口的机会。 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
),