几年前,有人给我看了下面的命令,使变量为零。
xor i,i
他告诉我这比给它分配零要快得多。是真的吗?编译器是否进行优化以获得执行此类操作的代码?
发布于 2011-10-08 17:12:46
你可以自己试试,看看答案:
movl $0,%eax
xor %eax,%eax
组装然后拆卸:
as xor.s -o xor.o
objdump -D xor.o
然后得到
0: b8 00 00 00 00 mov $0x0,%eax
5: 31 c0 xor %eax,%eax
32位寄存器的mov指令是2.5倍大,从ram加载需要更长的时间,并且消耗了更多的缓存空间。回到过去,仅仅加载时间是一个杀手,今天的内存循环时间和缓存空间可能被认为没有那么明显,但是如果编译器和/或代码太频繁地这样做,您将看到缓存空间的丢失和更多的驱逐,以及更慢的系统内存周期。
在现代CPU中,较大的代码大小也可以减缓解码器的速度,可能会阻止它们每一个周期解码最大数量的x86指令。(例如,一些CPU在16B块中最多有4条指令。)
也有performance advantages to xor over mov in some x86 CPUs (especially Intel's) that have nothing to do with code-size,所以xor-零在x86程序集中总是首选的.
另一组实验:
void fun1 ( unsigned int *a )
{
*a=0;
}
unsigned int fun2 ( unsigned int *a, unsigned int *b )
{
return(*a^*b);
}
unsigned int fun3 ( unsigned int a, unsigned int b )
{
return(a^b);
}
0000000000000000 <fun1>:
0: c7 07 00 00 00 00 movl $0x0,(%rdi)
6: c3 retq
7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
e: 00 00
0000000000000010 <fun2>:
10: 8b 06 mov (%rsi),%eax
12: 33 07 xor (%rdi),%eax
14: c3 retq
15: 66 66 2e 0f 1f 84 00 nopw %cs:0x0(%rax,%rax,1)
1c: 00 00 00 00
0000000000000020 <fun3>:
20: 89 f0 mov %esi,%eax
22: 31 f8 xor %edi,%eax
24: c3 retq
在你的问题中指出变量xor i,i可能会导致什么。由于您没有指定您所指的是哪个处理器或上下文,所以很难画出整个画面。例如,如果您谈论的是C代码,您必须了解编译器对该代码做了什么,这在很大程度上取决于函数本身中的代码,如果在您的xor编译器将操作数放在寄存器中,并且取决于您的编译器设置,您可能会得到xor eax,eax。或者编译器可以选择将其更改为mov,0,或更改一个something=0;改为xor,reg。
还有更多的序列需要思考:
如果变量的地址已经在寄存器中:
7: c7 07 00 00 00 00 movl $0x0,(%rdi)
d: 8b 07 mov (%rdi),%eax
f: 31 c0 xor %eax,%eax
11: 89 07 mov %eax,(%rdi)
编译器将选择mov零而不是xor。如果您尝试了这个C代码,就会得到这样的结果:
void funx ( unsigned int *a )
{
*a=*a^*a;
}
编译器将其替换为移动零。获取的字节数相同,但需要访问两个内存而不是一个内存,并烧毁了一个寄存器。三条指令来执行而不是一条。所以移动零点明显更好。
现在,如果它是字节大小的,并且在寄存器中:
13: b0 00 mov $0x0,%al
15: 30 c0 xor %al,%al
代码大小没有差别。(但他们的执行方式仍然不同)。
如果你说的是另一个处理器,比如说ARM
0: e3a00000 mov r0, #0
4: e0200000 eor r0, r0, r0
8: e3a00000 mov r0, #0
c: e5810000 str r0, [r1]
10: e5910000 ldr r0, [r1]
14: e0200000 eor r0, r0, r0
18: e5810000 str r0, [r1]
您不能通过使用xor (独占或,eor)来保存任何内容:一条指令是一条指令,包括取走指令和执行指令。如果在寄存器中有变量的地址,就像任何处理器一样,在ram中xoring。如果您必须将数据复制到另一个寄存器以执行xor,那么您仍然有两个内存访问和三个指令。如果您有一个处理器可以对内存执行内存操作,则零位移动成本更低,因为根据处理器的不同,您只有一个内存访问权限和一个或两个指令。
事实上,更糟糕的是:由于内存排序规则,eor r0, r0, r0
是required to have an input dependency on r0
(限制无序执行)。Xor-归零总是产生零,但只有助于x86程序集中的性能。
因此,关键在于,如果您在从8088到现在的x86系统上使用汇编程序中的寄存器,xor通常会更快,因为指令更小、获取更快、缓存更少(如果有)、为其他代码留下更多缓存等等。同样地,需要在指令中编码零的非x86可变指令长度处理器也需要更长的指令、更长的获取时间、更多的缓存(通常取决于它的编码方式)。更糟糕的是,如果您有条件标志,并且希望那个move/xor设置零标志,那么您可能必须刻录正确的指令(在某些处理器上,mov不改变标志)。有些处理器有一个特殊的零寄存器,这不是一般的用途,当您使用它时,您可以得到一个零,这样您就可以对这个非常常见的用例进行编码,而不需要消耗更多的指令空间,或者燃烧额外的指令周期,立即将零加载到寄存器中。例如,移动一个0x1234将花费两个字的指令,但是移动0x0000或0x0001和一些其他常量可以被编码在一个指令字中。如果您说的是ram中的一个变量,读-修改-写两个内存周期(不包括指令获取),那么所有处理器都会双击内存,如果读导致缓存行填充(然后写入速度非常快),则会变得更糟,但是如果没有读,写可能会经过缓存并执行得非常快,因为处理器可以在写同时运行(有时您会获得性能增益,有时不会,如果您为它进行调优的话)。x86和可能较旧的处理器是您看到xinging而不是移动零的习惯的原因。对于这些特定的优化,性能提高仍然存在,系统内存仍然非常缓慢,任何额外的内存周期都是昂贵的,同样地,丢弃的任何缓存都是昂贵的。半途而废的编译器,即使是gcc,也会检测到一个xor i,我相当于i=0,并逐案选择更好的指令序列(在一般系统上)。
拿一份迈克尔·阿布拉什的“集会禅宗”。好的,用过的拷贝是一个合理的价格(低于50美元),即使你去买80美元,它是非常值得的。试着超越特别的8088“循环食客”,了解他试图教的一般思想过程。然后花费尽可能多的时间来分解您的代码,最好是用于许多不同的处理器。运用你所学到的..。
发布于 2011-10-08 07:10:11
在较旧的CPU上(但那些在Pentium Pro之后的CPU,如注释所示),过去是这样的,然而,现在大多数现代CPU都有特殊的热路径,用于零分配(寄存器和对齐的变量),这应该会产生同等的性能。大多数现代编译器将倾向于使用这两者的混合,这取决于周围的代码(老的MSVC编译器总是在优化的构建中使用XOR
,而且它仍然相当多地使用XOR
,但在某些情况下也会使用MOV reg,0
)。
这在很大程度上是一种微观优化,所以tbh,您可以只做任何最适合您的事情,除非您有由于注册依赖而滞后的紧循环。但是,应该注意的是,大多数时间使用XOR
占用的空间较少,这对于嵌入式设备或当您尝试对齐分支目标时都是很好的。
这假设您主要指的是x86及其衍生物,在这一点上@Pascal给了我一个想法,把它作为基础的技术参考。英特尔优化手册有两个部分处理这个问题,即2.1.3.1 Dependancy Breaking Idioms
和3.5.1.7 Clearing Registers and Dependancy Breaking Idioms
。这两个部分基本主张使用基于XOR
的指令进行任何形式的寄存器清除,因为它具有依赖性破坏的特性(这可以消除延迟)。但在条件码需要保存的部分,则更倾向于将MOV
=0放入寄存器中。
发布于 2011-10-10 13:46:36
由于xor指令较短,预取队列对内存带宽的限制,因此在8088上肯定是正确的(较小程度上是8086)。
https://stackoverflow.com/questions/7695309
复制相似问题