为什么 `mov %eax, %eax; nop` 比 `nop` 更快?

5

显然,现代处理器可以检测到你像将一个寄存器移动到它本身( mov %eax, %eax )这样愚蠢的行为,并将其优化掉。为了验证该说法,我运行了以下程序:

#include <stdio.h>
#include <time.h>

static inline void f1() {
   for (int i = 0; i < 100000000; i++)
      __asm__(
            "mov %eax, %eax;"
            "nop;"
            );
}

static inline void f2() {
   for (int i = 0; i < 100000000; i++)
      __asm__(
            "nop;"
            );
}

static inline void f3() {
   for (int i = 0; i < 100000000; i++)
      __asm__(
            "mov %ebx, %eax;"
            "nop;"
            );
}

int main() {
   int NRUNS = 10;
   clock_t t, t1, t2, t3;

   t1 = t2 = t3 = 0;
   for (int run = 0; run < NRUNS; run++) {
      t = clock(); f1(); t1 += clock()-t;
      t = clock(); f2(); t2 += clock()-t;
      t = clock(); f3(); t3 += clock()-t;
   }

   printf("f1() took %f cycles on avg\n", (float) t1/ (float) NRUNS);
   printf("f2() took %f cycles on avg\n", (float) t2/ (float) NRUNS);
   printf("f3() took %f cycles on avg\n", (float) t3/ (float) NRUNS);

   return 0;
}

这给我带来了:
f1() took 175587.093750 cycles on avg
f2() took 188313.906250 cycles on avg
f3() took 194654.296875 cycles on avg

预料之中,f3() 的速度最慢。但令人惊讶的是(至少对我来说),f1()f2() 更快。为什么会这样呢?
更新:使用 -falign-loops 进行编译得到的结果基本相同。
f1() took 164271.000000 cycles on avg
f2() took 173783.296875 cycles on avg
f3() took 177765.203125 cycles on avg

1
肯定很奇怪。你在哪个处理器上测试过?使用了什么编译器和什么标志?此外,请注意clock()不计算周期,而是CLOCKS_PER_SEC,因此打印%f周期是误导性的。另外,尽量不要将其转换为float,而可以使用double - fuz
1
请尝试启用优化再次执行操作,因为如果没有优化,可能会发生奇怪的事情。另外,请尝试更改代码,使每个示例运行至少一秒钟,以避免预热或类似问题。您是在32位模式还是64位模式下编译? - fuz
4
你正在测量的测试主要是循环开销。对于这样一个微小的循环体,其他因素可能会占主导地位,比如跳转目标的对齐方式。 - Raymond Chen
1
不是解释,但我注意到在你的代码示例中,你实际上没有执行 mov eax, eax,而是执行了 mov ebx, eax - 500 - Internal Server Error
1
@BlenderBender 在我的电脑上(Intel(R) Xeon(R) W-2133 CPU @ 3.60GHz),我得到了142678.703125/142067.000000/143752.093750的结果,没有进行优化但使用了-falign-loops。使用优化后,代码不会终止,因为您覆盖了eax - fuz
显示剩余13条评论
1个回答

3
这段链接文章让我认为这可以被优化掉的部分是:“move函数负责检查等效位置”。这是在谈论SBCL中的(move r x)函数,而不是x86的mov指令。它谈论的是从低级中间语言生成代码时的优化,而不是硬件在运行时的优化。无论mov%eax,%eax还是nop都不是完全免费的。它们都需要前端吞吐量,而mov%eax,%eax在64位模式下甚至不是NOP(它将EAX零扩展到RAX,因为它是相同的寄存器移动消除在英特尔CPU上失败)。请参见Can x86's MOV really be "free"? Why can't I reproduce this at all? 以获取有关前端/后端吞吐量瓶颈与延迟的更多信息。
您可能正在看到代码对齐的副作用,或者像在没有优化编译的情况下添加冗余赋值可以加速代码中那样的Sandybridge系列存储转发延迟效应,因为您还禁用了优化编译,让编译器生成反优化代码以进行一致的调试,使循环计数器保持在内存中。(通过存储/重新加载的6个周期循环传递依赖链,而不是普通微小循环每个时钟周期1次迭代。)
如果您的结果在更大的迭代次数下可重现,则可能存在某种微架构解释,但这可能与您尝试测量的任何内容无关。
当然,您还需要修复f3中的mov %ebx,%eax;错误才能成功启用优化编译。未告知编译器就破坏EAX会影响编译器生成的代码。由于您没有解释您试图通过该代码测试什么,所以我不知道它是否是打字错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接