x + =比x = x + a快吗?


84

我正在阅读Stroustrup的“ C ++编程语言”,他说在两种向变量添加内容的方法中

x = x + a;

x += a;

他喜欢,+=因为它很可能会更好地实施。我认为他的意思是它的运行速度也更快。
但是真的吗?如果它取决于编译器和其他因素,如何检查?


45
“ C ++编程语言”于1985年首次发布。最新版本于1997年发布,1997年版本的特殊版本于2000年发布。因此,某些部分已经过时了。
JoeG 2012年

5
两条线可能会做一些完全不同的事情。您必须更具体。
Kerrek SB 2012年


26
现代编译器足够聪明,可以将这些问题视为“过时的”。
gd1 2012年

2
重新打开它,因为重复的问题询问的是C而不是C ++。
2013年

Answers:


212

任何编译器称职将产生完全相同的两个构建任何内置类型(同样的机器语言程序intfloat等等),只要声明确实是那样简单x = x + a; 和优化启用。(值得注意的是,GCC-O0是默认模式,它执行反优化,例如将完全不必要的存储插入到内存中,以确保调试器始终可以找到变量值。)

但是,如果声明更加复杂,则它们可能会有所不同。假设f有一个函数返回一个指针,然后

*f() += a;

f只打一次电话,而

*f() = *f() + a;

叫两次。如果f有副作用,则两者之一将是错误的(可能是后者)。即使f没有副作用,编译器也可能无法消除第二个调用,因此后者的确可能会更慢。

既然我们在这里谈论的是C ++,对于重载operator+和的类类型,情况就完全不同了operator+=。如果x是这样的类型,那么-在优化之前-会x += a转换为

x.operator+=(a);

x = x + a翻译成

auto TEMP(x.operator+(a));
x.operator=(TEMP);

现在,如果正确编写了类并且编译器的优化器足够好,那么两者都将生成相同的机器语言,但是并不能像内置类型那样确定。这可能是Stroustrup鼓励使用时的想法+=


14
还有另一个方面-可读性。C ++成语添加exprvarvar+=expr,写它的其他方式将读者糊涂。
Tadeusz Kopec 2012年

21
如果您发现自己在写作*f() = *f() + a;,则可能不妨认真研究一下您真正想要实现的目标……
Adam Davis

3
而且如果var = var + expr使您感到困惑,但是var + = expr没有使您感到困惑,那么您就是我见过的最奇怪的软件工程师。两者都是可读的;只需确保您保持一致即可(我们都使用op =,因此无论如何它都
等于

7
@PiotrDobrogost:回答问题怎么了?无论如何,发问者应检查重复项。
Gorpik 2012年

4
@PiotrDobrogost在我看来似乎有点...嫉妒...如果您想四处寻找重复的东西,那就去吧。我一个人更喜欢回答问题,而不是寻找骗子(除非这是我之前特别记得的一个问题)。有时可能会更快,因此ergo可以更快地帮助提出问题的人。另外,请注意,这甚至不是循环。1是一个常数,a可以是易失性,用户定义的类型或其他类型。完全不同。实际上,我什至不知道这是怎么结束的。
Luchian Grigore 2012年

56

您可以通过查看dissasembly检查,这是相同的。

对于基本类型,两者都同样快。

这是调试版本生成的输出(即没有优化):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

对于用户定义的类型,您可以在其中重载operator +operator +=,这取决于它们各自的实现。


1
并非在所有情况下都是如此。我发现将内存地址加载到寄存器中,对其进行递增和写回比直接增加内存位置(不使用原子)更快。我会尝试将代码沙沙作响
James

用户定义的类型如何?好的编译器生成等效的程序集,但没有这种保证。
mfontanini 2012年

1
@LuchianGrigore Nope,如果a为1,xvolatile编译器可以生成inc DWORD PTR [x]。太慢了
詹姆斯

1
@Chiffa不是依赖于编译器的,而是依赖于开发人员的。您可以执行operator +任何操作,并operator +=计算第100000个素数,然后返回。当然,这将是一件愚蠢的事情,但这是可能的。
Luchian Grigore 2012年

3
@James:如果您的程序对++x和之间的性能差异很敏感temp = x + 1; x = temp;,那么很可能应该用汇编而不是c ++来编写...
EmirCalabuch 2012年

11

是! 对于后者x可能产生副作用的情况,它编写起来更快,阅读起来更快,弄明白了。因此,对于人类而言,总体而言更快。通常,人工时间比计算机时间花费更多,因此这一定是您要问的。对?


8

它实际上取决于x和a的类型以及+的实现。对于

   T x, a;
   ....
   x = x + a;

编译器在评估它时必须创建一个临时T来包含x + a的值,然后可以将其分配给x。(在此操作期间,它不能使用x或a作为工作空间)。

对于x + = a,它不需要临时的。

对于琐碎的类型,没有区别。


8

之间的区别x = x + a,并x += a为机器要经过的工作量-一些编译器可能(通常)优化它拿走,但通常情况下,如果我们忽视优化了一会儿,什么情况是,在前者的代码片段中,机器必须对值进行x两次查找,而在后一种情况下,该查找只需进行一次。

但是,正如我提到的那样,当今大多数编译器都足够智能,可以分析指令并减少所需的最终机器指令。

PS:关于堆栈溢出的第一个答案!


6

正如标记为C ++一样,您无法从已发布的两个语句中得知。您需要知道什么是“ x”(有点像答案“ 42”)。如果x是POD,那么它并不会带来太大的变化。但是,如果x是类,则operator +andoperator +=方法可能会有重载,这些重载可能具有不同的行为,从而导致非常不同的执行时间。


6

如果您说+=您正在使编译器的工作变得容易得多。为了使编译器认识到x = x+a与相同x += a,编译器必须

  • 分析左侧(x),以确保它没有副作用,并且始终引用相同的L值。例如,它可以是z[i],并且必须确保zi不变。

  • 分析右手边(x+a)并确保它是一个求和,并且左手边在右手边发生一次且仅发生一次,即使它可以进行变换(如)z[i] = a + *(z+2*0+i)

如果你的意思是添加ax,编译器作者赞赏它,当你刚刚说你的意思。这样,您就不会行使编译器的编写者希望他/她摆脱了所有错误的那一部分,并且这实际上并不会使您的生活更轻松,除非您坦白地说无法直截了当Fortran模式。


5

举一个具体的例子,想象一个简单的复数类型:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

对于a = a + b的情况,它必须制作一个额外的临时变量,然后将其复制。


这是一个非常好的示例,显示了如何实现2个运算符。
Grijesh Chauhan

5

您问错了问题。

这不太可能提高应用程序或功能的性能。即使是这样,找出问题的方法是分析代码并确定它对您的影响。与其在此级别上担心哪个速度更快,不如从清晰度,正确性和可读性方面考虑更为重要

当您考虑到即使这是重要的性能因素时,编译器也会随着时间的推移而发展,这一点尤其正确。有人可能会想出一种新的优化方法,今天正确的答案明天可能会出错。这是过早优化的经典案例。

这并不是说性能根本不重要……只是实现您的绩效目标是错误的方法。正确的方法是使用分析工具来了解代码实际在哪里花费时间,从而在哪里集中精力。


当然,但这只是一个低级的问题,而不是一个大问题,“我何时应该考虑这种差异”。
Chiffa

1
OP的问题是完全合法的,如其他人的回答(和赞成票所示)所示。尽管我们明白了您的意思(首先介绍了此类信息),但了解此类内容绝对有趣-您是否真的要对您编写的每条微不足道的语句进行分析,对您做出的每个决策的结果进行分析?即使SO上已经有人进行了研究,分析,分解了案例并有一个一般性的答案可以提供?

4

我认为这应该取决于机器及其架构。如果其架构允许间接内存寻址,则编译器编写器可能会改用以下代码(用于优化):

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

i = i + y可能会转换为(没有优化):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


也就是说,i还应该考虑其他复杂性,例如if是否是函数返回指针等。大多数生产级别的编译器,包括GCC,都为两个语句生成相同的代码(如果它们是整数)。


2

不,两种方式都一样。


10
如果它是带有重载运算符的用户定义类型,则不是。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.