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

86

我正在阅读 Stroustrup 的《C++ 程序设计语言》,他提到了两种将值加到变量中的方式。

x = x + a;

x += a;

他更喜欢使用+=,因为它很可能被更好地实现了。我想他的意思是这样做速度更快。
但是真的吗?如果它取决于编译器和其他因素,我该如何检查?


47
《C++程序设计语言》首次出版于1985年,最近一版发行于1997年,2000年还出版了该版本的特别版。因此,部分内容已经严重过时。 - JoeG
5
这两行代码可能会执行完全不同的操作。你需要更具体一些。 - Kerrek SB
27
现代编译器已经足够智能,以至于这些问题被视为“过时”。 - gd1
2
重新打开此问题,因为重复的问题询问的是C而不是C ++。 - Kev
显示剩余7条评论
11个回答

216

只要语句真的和 x = x + a; 一样简单并且启用了优化,任何值得一用的编译器都会为内置类型(intfloat等)生成完全相同的机器语言序列。 (值得注意的是,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++中将 expr 加到 var 的惯用法是var+=expr,反其道而行之会使读者感到困惑。 - Tadeusz Kopec for Ukraine
21
如果你发现自己写了 *f() = *f() + a; 这样的代码,你可能需要好好审视一下自己真正想要实现什么... - Adam Davis
3
如果 var=var+expr 让你感到困惑,但 var+=expr 不会,那么你是我见过最奇怪的软件工程师。两种写法都可读性良好;只要保持一致即可(我们所有人都使用 op=,所以这是无意义的 =P)。 - WhozCraig
7
回答问题有什么问题吗?无论如何,应该由提问者自己检查是否有重复。 - Gorpik
4
在我看来,你有点儿……嫉妒。如果你想四处寻找重复内容,那就去做吧。至于我,我更喜欢回答问题,而不是寻找重复内容(除非这是我特别记得以前看过的问题)。有时候这样做可以更快,也就能更快地帮助提问者。另外,请注意,这甚至不是一个循环。“1”是一个常量,“a”可以是易变量、用户定义类型或其他任何东西。完全不同。事实上,我不明白这个问题怎么会被关闭的。 - Luchian Grigore
显示剩余13条评论

57

通过查看反汇编结果,你可以进行检查,结果会是相同的。

对于基本类型,两者速度是一样的。

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

    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
1
@LuchianGrigore 不,如果 a 是1且 xvolatile,编译器可能会生成 inc DWORD PTR [x]。这很慢。 - James
1
@Chiffa 不是依赖于编译器,而是依赖于开发者。你可以实现 operator + 使其无效,然后让 operator += 计算第 100000 个质数并返回结果。当然,这样做很蠢,但这是可能的。 - Luchian Grigore
3
如果你的程序能够感知 ++xtemp = x + 1; x = temp; 的性能差异,那么很可能应该用汇编语言而不是 C++ 编写... - EmirCalabuch
显示剩余12条评论

11
x = x + ax += a 的区别在于机器所需的工作量——一些编译器可以(而且通常会)将其优化掉,但通常情况下,如果我们暂时忽略优化,前者的代码片段中,机器需要查找 x 的值两次,而后者只需要进行一次查找。
然而,正如我之前提到的,今天大多数编译器都足够智能,可以分析指令并减少所需的机器指令。
PS:这是 Stack Overflow 上的第一个答案!

10

是的!对于人类来说,使用简写更加快速,便于阅读和理解,特别是在变量 x 可能会产生副作用的情况下。因此,总体上对于人类而言更为迅速。一般来说,人类时间的成本远高于计算机时间,所以这必定是你所询问的问题。对吧?


8

这真的取决于x和a的类型以及+的实现方式。对于

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

编译器必须创建一个临时变量T来包含x + a的值,同时对其进行评估,然后将其赋给x。在此操作期间,它不能使用x或a作为工作区。

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

对于简单类型,没有区别。


8
如果你使用+=,将使编译器的工作变得更加容易。为了让编译器认识到x = x+ax += a是相同的,编译器必须:
  • 分析左边表达式(x),确保它没有副作用并始终引用同一l-value。例如,它可以是z[i],必须确保zi都没有改变。

  • 分析右边表达式(x+a),确保它是一个求和,并且左边的表达式只出现一次,即使它可以被转换,例如z[i] = a + *(z+2*0+i)

如果你的意思是将a加到x上,那么编译器作者会感激你直截了当地表达你的意思。 这样,你就不会让编译器执行它希望消除所有错误的部分了,而这也不会让你的工作变得更容易,除非你无法脱离Fortran模式。

6
由于您将此标记为C++,因此无法从您发布的两个语句中知道答案。您需要知道'x'是什么(这有点像答案“42”)。如果x是POD,则它不会真正产生太大的影响。但是,如果x是一个类,则可能对operator +operator +=方法进行重载,这可能会导致非常不同的执行时间。

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
2
OP的问题是完全合理的,正如其他人的答案(和赞)所显示的那样。尽管我们明白你的观点(先建立个人资料等),但了解这种信息肯定是有趣的 - 你真的会为你写的每一个琐碎的声明进行分析吗?为你做出的每个决定进行分析吗?即使在SO上已经有人研究、分析、拆解了这个案例并提供了一般性的答案? - user948581

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 是否是返回指针的函数等。大多数生产级别的编译器,包括 GCC,在这两个语句(如果它们是整数)上产生相同的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接