【发布时间】:2010-11-11 06:19:31
【问题描述】:
有两种众所周知的方法可以在 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 指令没有依赖性?有什么更好的测试来检查这一点?
【问题讨论】:
-
我认为这就是我们使用高级语言的原因。如果您真的想知道,只需将 codegen 阶段更改为做一个或另一个。基准。选择最好的。
-
啊,老
xor reg, reg把戏 - 美好的旧时光 :) -
我认为 x86 架构明确定义 XOR reg,reg 以打破对 reg 的依赖。请参阅英特尔架构手册。我希望 MOV reg,... 做同样的事情仅仅是因为它是一个 MOV。所以你真正的选择是,如果你不关心状态位(异或会损坏它们),哪一个占用更少的空间(我猜执行时间是一样的)。
-
您的
Count变量溢出,因此循环运行的周期比您预期的要少得多 -
在最近的微架构上,
xor reg,reg不需要执行单元(在解码中处理?)。它打破了对reg的依赖,并且部分标志更新停止。而且它的编码更小。在最近的 x86-64 上使用mov方法没有充分的理由,除非您必须保留 [e] 标志。
标签: assembly x86 micro-optimization