使用xor reg,reg优于mov reg,0吗?

在x86上有两种众所周知的方法来将整数寄存器设置为零值。

mov reg, 0 

要么

 xor reg, reg 

有一种观点认为,第二个变体是更好的,因为值0没有存储在代码中,并节省了生成的机器代码的几个字节。 这绝对是好的 – 使用更less的指令caching,这有时可以允许更快的代码执行。 许多编译器产生这样的代码。

然而,异或指令和改变相同寄存器的先前指令之间存在正式的指令间依赖关系。 由于存在依赖性,后者的指令需要等到前一个指令完成,这可以减less处理器单元的负载并伤害性能。

 add reg, 17 ;do something else with reg here xor reg, reg 

很显然,无论初始寄存器值如何,异或的结果都是完全相同的。 但是处理器能够识别这个吗?

我在VC ++ 7中尝试了以下testing:

 const int Count = 10 * 1000 * 1000 * 1000; int _tmain(int argc, _TCHAR* argv[]) { int i; DWORD start = GetTickCount(); for( i = 0; i < Count ; i++ ) { __asm { mov eax, 10 xor eax, eax }; } DWORD diff = GetTickCount() - start; start = GetTickCount(); for( i = 0; i < Count ; i++ ) { __asm { mov eax, 10 mov eax, 0 }; } diff = GetTickCount() - start; return 0; } 

对两个循环进行优化的时间完全相同。 这是否合理地certificate处理器认识到在先前的mov eax, 0指令中没有xor reg, reg指令的依赖关系? 什么可能是一个更好的testing来检查这个?

一个真正的答案给你:

Intel 64和IA-32体系结构优化参考手册

3.5.1.8节是你想看的地方。

总之,在某些情况下,可能首选xor或mov。 问题集中在依赖链和条件码的保存上。

我卖掉1966年的HR旅行车后,我停止了修理自己的汽车。 我在类似的修复与现代的CPU 🙂

这确实取决于底层的微码或电路。 CPU很有可能识别"XOR Rn,Rn"并简单地将所有位全部清零而不用担心内容。 但是,当然,它也可以用"MOV Rn, 0"做同样的事情。 一个好的编译器会为目标平台select最好的变体,所以这通常只是一个问题,如果你使用汇编编码。

如果CPU足够智能,则XOR依赖关系消失,因为它知道该值是无关紧要的,并将其设置为零(这又取决于正在使用的实际CPU)。

不过,我早已过去关心我的代码中的几个字节或几个时钟周期 – 这看起来像是微型优化发疯了。

x86有可变长度的指令。 MOV EAX,0需要比XOR EAX,EAX多一个或两个字节的代码空间。

在现代CPU上,XOR模式是首选。 它更小,更快。

更小的实际上是重要的,因为在许多真实的工作负载中,限制性能的主要因素之一是icaching未命中。 这并不是在微观基准testing中比较两个选项,但是在现实世界中,它会使代码运行得稍微快一些。

而且,忽略减less的Icaching未命中,在任何CPU在过去多年XOR是相同的速度或更快的MOV。 什么比执行MOV指令更快? 根本不执行任何指令! 在最近的英特尔处理器上,分派/重命名逻辑识别异或模式,“实现”结果将为零,并且只是将寄存器指向物理零寄存器。 然后它抛弃了指令,因为不需要执行它。

最终结果是异或模式使用零执行资源,并且在最近的英特尔CPU上可以每个周期“执行”四条指令。 MOV每个周期三条指令。

有关详细信息,请参阅我写的这篇博客文章:

https://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/

大多数程序员不应该担心这个问题,但是编译器编写者不用担心,理解正在生成的代码是很好的,而且只是很酷!

我认为在早期的体系结构中, mov eax, 0指令所用的时间比xor eax, eax还要长…不能完全记得原因。 除非你有更多的mov s,但是我会想象你不可能导致由于存储在代码中的那一个文字而引起的caching未命中。

另外请注意,从内存来看,这些方法的标志状态并不相同,但我可能会误解这一点。

正如其他人所指出的,答案是“谁在乎?”。 你在编写一个编译器吗?

在第二个音符上,你的基准testing可能不会奏效,因为你有一个分支,可能需要所有的时间。 (除非你的编译器为你展开循环)

另一个不能在循环中testing单个指令的原因是所有代码都将被caching(与真实代码不同)。 所以你已经把mov eax,0和xor eax,eax之间的大小差别放在了L1caching的整个时间之外。

我的猜测是,现实世界中任何可衡量的性能差异都将归因于caching的大小差异,而不是由于这两个选项的执行时间。