我想知道,主要是出于好奇,如果使用相同的寄存器进行操作比使用两个更好。考虑到表现和/或其他问题会有什么好处呢?
mov %rbx, %rcx
imul %rcx, %rcx
或
mov %rbx, %rcx
imul %rbx, %rcx
关于如何对此进行基准测试的任何提示,或者我可以阅读有关此类事物的资源,我将不胜感激,因为我不熟悉汇编。
答案 0 :(得分:5)
在现代处理器上,对源和目标使用一个寄存器并使用两个不同的寄存器将永远不会对性能产生任何影响。其原因部分归因于register renaming,如果性能存在差异,可以通过将其中一个寄存器更改为另一个寄存器来解决它,并修改后续指令以使用新寄存器(您的处理器实际上有比指令集更多的寄存器有一种引用它们的方式,以便它可以做这样的事情)。这也是因为流水线处理器的实现的性质 - 源寄存器的内容在一个流水线阶段读取,然后在另一个后期写入,这使得单个寄存器的使用变得困难或不可能指导引起任何类型的互动,比如你担心的那种互动。
更有问题的是,如果一条指令引用了其前一条指令中产生的值,但即便通过out-of-order execution解决了该问题。
答案 1 :(得分:4)
我可以阅读有关此类事物的资源
请参阅Agner Fog's microarch pdf及其优化装配指南。还有x86标记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
如果imul
位于关键路径上,mov
具有非零延迟(如AMD CPU和IvyBridge之前的Intel),则可能更好。 imul
的结果将提前一个周期准备好,因为它不依赖于mov
的结果。
但是,如果旧值在关键路径上且平方值不在,那么这会更糟糕,因为它会向关键路径添加mov
。
当然,这也意味着您必须跟踪旧变量现在位于不同寄存器中的事实,并且旧寄存器具有平方值。如果这是循环中的问题,请将其展开以便最终得到循环顶部所期望的内容。如果您希望这很容易,那么您可以使用编译器而不是手动优化asm。
但是,英特尔P6系列CPU(PPro / PII到Nehalem)具有有限的寄存器读取端口,因此最好选择刚刚读取的寄存器。如果%rbx
未在最后几个周期写入,则当mov
和imul
uops通过重命名和发布阶段时,必须从永久寄存器文件中读取%rbx
RAT)。
如果他们没有作为同一组4的一部分发布,那么他们每个人都需要分别阅读mov
。由于Core2 / Nehalem中的寄存器文件只有3个读端口,因此问题组(四重奏,如Agner Fog调用它们)一直停止,直到从寄存器文件中读取所有最近写入的输入寄存器值(每个周期3个,或者2在Core2上,3个寄存器中没有一个是寻址模式下的索引寄存器。)
有关详细信息,请参阅Agner Fog's microarch pdf第8.8节。 Core2部分返回PPro部分。 PPro有一个3宽的管道,因此在那一节中,Agner谈论的是三胞胎,而不是四重奏。
如果imul
和%rbx
一起发布,则它们都会共享mov %rbx, %rcx
imul %rcx, %rcx # uses only the recently-written rcx; can't contribute to register-read stalls
的相同内容。 Core2 / Nehalem有三分之一的机会发生这种情况。
在您提到的第一个序列之间选择具有明显(但通常很小)优势的英特尔P6系列CPU。其他CPU,AFAIK没有区别,所以选择很明显。
mov %rbx, %rcx
imul %rbx, %rcx # can't execute until after the mov, but still reads a potentially-old register
这两个世界都是最糟糕的:
mov reg,reg
如果您要依赖最近编写的寄存器,您最好只使用 最近编写的寄存器。
英特尔Sandybridge系列使用物理寄存器文件(如AMD Bulldozer系列),并且没有寄存器读取停顿。
Ivybridge(第二代Sandybridge)以及稍后在寄存器重命名时处理rbx
,没有延迟且没有执行单元。这意味着,就关键路径长度而言,您是否rcx
或mov
无关紧要。
然而,AMD Bulldozer系列只能在其重命名阶段处理xmm寄存器移动;整数寄存器移动仍有1c延迟。
如果延迟是循环中每次循环的周期的限制因素,那么imul %rbx, %rcx
所依赖的依赖链仍然值得关注。
如何对此进行基准测试
我认为你可以把一个microbenchmark放在一起,它在Core2上有imul %rcx, %rcx
的寄存器读取停顿,而不是mov
。但是,这需要一些试验和错误才能让imul
和lea (%rsi, %rdi, 1), %eax
在不同的群组中发布,除非您感觉非常有创意,否则可能会出现一些人工环境的代码很多寄存器。 (例如add (%rsi, %rdi, 1), %eax
,甚至{{1}}(必须读取所有三个寄存器,并且在core2 / nehalem上进行微融合,因此在一个问题组中只需要1个uop插槽。({{3 }}))。