使用xor reg,reg是否优于mov reg,0?

时间:2009-07-16 06:06:01

标签: assembly x86 micro-optimization

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

无论

mov reg, 0

xor reg, reg

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

然而,xor指令和更改同一寄存器的早期指令之间正式存在指令间依赖关系。由于存在依赖性,后一条指令需要等到前者完成,这可能会减少处理器单元的负载并损害性能。

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

很明显,无论初始寄存器值如何,xor的结果都完全相同。但是处理器能够识别出来吗?

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

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;
}

关闭优化两个循环完全相同的时间。这是否合理地证明处理器认识到xor reg, reg指令对早期mov eax, 0指令没有依赖性?什么可以是更好的测试来检查这个?

6 个答案:

答案 0 :(得分:29)

给你一个真正的答案:

Intel 64 and IA-32 Architectures Optimization Reference Manual

第3.5.1.8节是你想看的地方。

简而言之,有些情况下可能首选xor或mov。这些问题围绕着依赖链和条件代码的保存。

答案 1 :(得分:13)

在我卖掉1966年的人力资源旅行车后,我停止了修理自己的汽车。我对现代CPU有类似的修复: - )

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

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

然而,在我的代码中,我已经过去了几个字节或几个时钟周期 - 这看起来像微优化很疯狂。

答案 2 :(得分:11)

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

答案 3 :(得分:10)

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

小实际上确实很重要,因为在许多实际工作负载上,限制性能的主要因素之一是i-cache未命中。这不会在比较这两个选项的微基准测试中捕获,但在现实世界中,它将使代码运行得更快。

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

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

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

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

大多数程序员不应该担心这一点,但编译器编写者必须要担心,理解正在生成的代码是很好的,而且它很酷!很酷!

答案 4 :(得分:2)

我认为在早期的体系结构中,mov eax, 0指令过去比xor eax, eax稍微长一点......不能回想起原因。除非你有更多的mov,否则我会想象你不会因为存储在代码中的那个文字而导致缓存未命中。

另请注意,从内存中标记的状态在这些方法之间并不相同,但我可能会错误地记住这一点。

答案 5 :(得分:-8)

正如其他人所说,答案是“谁在乎?”。你在写编译器吗?

在第二个注释中,您的基准测试可能无法正常工作,因为您在那里有一个分支,可能总是花费所有时间。 (除非你的编译器为你展开循环)

您无法在循环中对单个指令进行基准测试的另一个原因是您的所有代码都将被缓存(与实际代码不同)。所以你已经在mov eax,0和xor eax之间取得了很大的差异,通过在整个时间内将它放在L1缓存中,eax离开了图片。

我的猜测是,在现实世界中任何可测量的性能差异都是由于占用缓存的大小差异,而不是由于两个选项的执行时间。