首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >零分配相对于xor,第二个真的更快吗?

零分配相对于xor,第二个真的更快吗?
EN

Stack Overflow用户
提问于 2011-10-08 07:01:13
回答 3查看 6.6K关注 0票数 17

几年前,有人给我看了下面的命令,使变量为零。

代码语言:javascript
运行
复制
xor i,i

他告诉我这比给它分配零要快得多。是真的吗?编译器是否进行优化以获得执行此类操作的代码?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2011-10-08 17:12:46

你可以自己试试,看看答案:

代码语言:javascript
运行
复制
  movl $0,%eax
  xor %eax,%eax

组装然后拆卸:

代码语言:javascript
运行
复制
as xor.s -o xor.o
objdump -D xor.o

然后得到

代码语言:javascript
运行
复制
   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程序集中总是首选的.

另一组实验:

代码语言:javascript
运行
复制
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。

还有更多的序列需要思考:

如果变量的地址已经在寄存器中:

代码语言:javascript
运行
复制
   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代码,就会得到这样的结果:

代码语言:javascript
运行
复制
void funx ( unsigned int *a )
{
    *a=*a^*a;
}

编译器将其替换为移动零。获取的字节数相同,但需要访问两个内存而不是一个内存,并烧毁了一个寄存器。三条指令来执行而不是一条。所以移动零点明显更好。

现在,如果它是字节大小的,并且在寄存器中:

代码语言:javascript
运行
复制
13: b0 00                   mov    $0x0,%al
15: 30 c0                   xor    %al,%al

代码大小没有差别。(但他们的执行方式仍然不同)。

如果你说的是另一个处理器,比如说ARM

代码语言:javascript
运行
复制
   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, r0required 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“循环食客”,了解他试图教的一般思想过程。然后花费尽可能多的时间来分解您的代码,最好是用于许多不同的处理器。运用你所学到的..。

票数 32
EN

Stack Overflow用户

发布于 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 Idioms3.5.1.7 Clearing Registers and Dependancy Breaking Idioms。这两个部分基本主张使用基于XOR的指令进行任何形式的寄存器清除,因为它具有依赖性破坏的特性(这可以消除延迟)。但在条件码需要保存的部分,则更倾向于将MOV=0放入寄存器中。

票数 5
EN

Stack Overflow用户

发布于 2011-10-10 13:46:36

由于xor指令较短,预取队列对内存带宽的限制,因此在8088上肯定是正确的(较小程度上是8086)。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7695309

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档