sha*_*oth 48 x86 assembly micro-optimization
在x86上有两种众所周知的方法可以将整数寄存器设置为零值.
或
mov reg, 0
Run Code Online (Sandbox Code Playgroud)
要么
xor reg, reg
Run Code Online (Sandbox Code Playgroud)
有一种观点认为第二种变体更好,因为值0没有存储在代码中并且节省了几个字节的生成的机器代码.这绝对是好的 - 使用较少的指令缓存,这有时可以实现更快的代码执行.许多编译器生成这样的代码.
然而,在xor指令和改变相同寄存器的早期指令之间正式存在指令间依赖性.由于存在依赖性,后一条指令需要等到前者完成,这可能会减少处理器单元的负载并损害性能.
add reg, 17
;do something else with reg here
xor reg, reg
Run Code Online (Sandbox Code Playgroud)
很明显,无论初始寄存器值如何,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;
}
Run Code Online (Sandbox Code Playgroud)
通过优化两个循环完全相同的时间.这是否合理地证明处理器认识到xor reg, reg
指令对先前mov eax, 0
指令没有依赖性?什么可以是一个更好的测试来检查这个?
ajs*_*410 13
x86具有可变长度指令.MOV EAX,0在代码空间中需要比XOR EAX,EAX多一个或两个字节.
pax*_*blo 12
我卖掉1966年的人力资源旅行车后,我停止了修理自己的车.我对现代CPU有类似的修复:-)
它实际上将取决于底层的微码或电路.很可能CPU可以识别"XOR Rn,Rn"
并简单地将所有位清零而不用担心内容.但是,当然,它可能会做同样的事情"MOV Rn, 0"
.一个好的编译器会为目标平台选择最好的变体,所以如果你在汇编程序中编码,这通常只是一个问题.
如果CPU足够智能,您的XOR
依赖关系就会消失,因为它知道该值是无关紧要的,并且无论如何都会将其设置为零(这又取决于所使用的实际CPU).
但是,我很久以前在我的代码中关注了几个字节或几个时钟周期 - 这似乎是微优化变得疯狂.
Bru*_*son 12
在现代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/
大多数程序员不应该担心这一点,但编译器编写者必须担心,并且理解正在生成的代码是好的,而且它很酷!