我可以在其中阅读有关此类内容的资源
See 阿格纳·福格的微建筑 pdf http://agner.org/optimize/,以及他的优化装配指南。还有其他链接x86 /questions/tagged/x86tag wiki(例如英特尔的优化手册)。
您没有提到的有趣选项是:
mov %rbx, %rcx
imul %rbx, %rbx # doesn'y have to wait for mov to execute
# old value of %rbx is still available in %rcx
If the imul
处于关键路径上,并且mov
具有非零延迟(如 AMD CPU 和 IvyBridge 之前的 Intel),这可能更好。的结果imul
将提前一个周期准备好,因为不依赖于结果mov
.
然而,如果旧值位于关键路径上而平方值不在关键路径上,那么情况会更糟,因为它增加了mov
到关键路径。
当然,这也意味着您必须跟踪旧变量现在位于不同寄存器中的事实,并且旧寄存器具有平方值。如果这是循环中的问题,请将其展开,这样您就可以得到循环顶部所期望的结果。如果您希望这很容易,您可以使用编译器而不是手动优化 asm。
然而,Intel P6 系列 CPU(PPro/PII 至 Nehalem)具有有限的寄存器读取端口,因此最好优先读取您刚刚编写的寄存器。如果%rbx
没有在最后几个周期写入,当mov
and imul
uop 经历重命名和发布阶段(RAT)。
如果他们不作为同一组 4 人的一部分发布,那么他们每个人都需要阅读%rbx
分别地。由于 Core2/Nehalem 中的寄存器文件只有 3 个读取端口,因此发行组(Agner Fog 称之为四重奏)会停止,直到从寄存器文件中读取所有最近未写入的输入寄存器值(每个周期 3 个,或Core2 上的 2 表示 3 个寄存器中没有一个是寻址模式下的索引寄存器)。
有关完整详细信息,请参阅阿格纳·福格的微建筑 pdf http://agner.org/optimize/第 8.8 节。 Core2 部分又引用了 PPro 部分。 PPro 有一个 3 宽的管道,因此 Agner 在该部分谈论的是三重奏,而不是四重奏。
If mov
and imul
一起发行,他们都有相同的阅读%rbx
。在 Core2/Nehalem 上发生这种情况的可能性为四分之三。
对于 Intel P6 系列 CPU,仅在您提到的序列之间进行选择,第一个序列比第二个序列具有明显(但通常很小)的优势。 AFAIK,其他 CPU 没有什么区别,所以选择是显而易见的。
mov %rbx, %rcx
imul %rcx, %rcx # uses only the recently-written rcx; can't contribute to register-read stalls
两全其美:
mov %rbx, %rcx
imul %rbx, %rcx # can't execute until after the mov, but still reads a potentially-old register
如果您要依赖最近编写的寄存器,您不妨使用only最近写入的寄存器。
Intel Sandybridge 系列使用物理寄存器文件(如 AMD Bulldozer 系列),并且没有寄存器读取停顿。
Ivybridge(第二代 Sandybridge)及后来的手柄mov reg,reg
在寄存器重命名时,零延迟且无执行单元。这意味着您是否愿意并不重要rbx
or rcx
至于关键路径长度。
然而,AMD Bulldozer 系列只能在重命名阶段处理 xmm 寄存器移动;整数寄存器移动仍然有 1c 延迟。
可能仍然值得关心哪个依赖链mov
如果延迟是循环每次迭代周期的限制因素,则为一部分。
如何对此进行基准测试
我认为你可以将一个在 Core2 上有寄存器读取停顿的微基准测试与imul %rbx, %rcx
,但不与imul %rcx, %rcx
。然而,这需要一些尝试和错误才能获得mov
and imul
在不同的组中发布,除非你真的很有创意,否则可能会有一些看起来很人造的周围代码,它们的存在只是为了读取大量寄存器。 (例如。lea (%rsi, %rdi, 1), %eax
, 甚至add (%rsi, %rdi, 1), %eax
(它必须读取所有三个寄存器,并且在 core2/nehalem 上进行微熔丝,因此它只需要问题组中的 1 个 uop 插槽。(它SnB 系列上没有微熔断器 https://stackoverflow.com/questions/26046634/micro-fusion-and-addressing-modes/31027695#31027695)).