85

我正在阅读 Stroustrup 的“The C++ Programming Language”,他说有两种方法可以向变量添加内容

x = x + a;

x += a;

他更喜欢+=它,因为它很可能得到更好的实施。我认为他的意思是它也工作得更快。
但真的吗?如果它取决于编译器和其他东西,我该如何检查?

4

11 回答 11

215

只要语句真的很简单并且启用了优化,任何称职的编译器都会为任何内置类型( , 等)的两种构造生成完全相同的机器int语言float序列。(值得注意的是,默认模式 GCC 会执行反优化,例如将完全不必要的存储插入内存,以确保调试器始终可以找到变量值。)x = x + a; -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 在鼓励使用+=.

于 2012-09-18T15:00:35.733 回答
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 +=,这取决于它们各自的实现。

于 2012-09-18T14:50:58.587 回答
10

是的!写起来快,读起来快,想出来也快,因为后者在x可能有副作用的情况下。因此,对于人类来说,它总体上更快。人力时间通常比计算机时间花费更多,所以这一定是您要问的。对?

于 2012-09-19T03:48:05.127 回答
9

x = x + a和之间的区别在于x += a机器必须经过的工作量 - 一些编译器可能(并且通常会)优化它,但通常,如果我们暂时忽略优化,会发生在前一个代码片段中,机器必须查找该值x两次,而在后一次中,此查找只需要发生一次。

然而,正如我所提到的,今天大多数编译器都足够智能,可以分析指令并减少所需的机器指令。

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

于 2012-09-18T18:09:39.853 回答
8

这实际上取决于 x 和 a 的类型以及 + 的实现。为了

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

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

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

对于平凡的类型,没有区别。

于 2012-09-18T14:56:09.823 回答
7

如果你说+=你让编译器的生活变得更轻松。为了让编译器识别它x = x+a与 相同x += a,编译器必须

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

  • 分析右手x+az[i] = a + *(z+2*0+i)

如果您的意思是添加ax,那么当您说出您的意思时,编译器作者会很感激。这样一来,您就不会使用编译器的编写者希望他/她解决所有错误的部分,这实际上并没有让您的生活变得更轻松,除非您真的无法摆脱困境Fortran 模式。

于 2012-09-18T18:57:02.117 回答
6

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

于 2012-09-18T14:56:04.743 回答
5

你问错问题了。

这不太可能推动应用程序或功能的性能。即使是这样,找出问题的方法是分析代码并确定它如何影响您。与其在这个级别上担心哪个更快,更重要的是从清晰、正确和可读性的角度进行思考。

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

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

于 2012-09-18T18:35:53.313 回答
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 的情况,它必须创建一个额外的临时变量,然后复制它。

于 2012-09-18T19:52:28.470 回答
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,为这两个语句生成相同的代码(如果它们是整数)。

于 2012-09-18T19:30:49.123 回答
2

不,两种方式的处理方式相同。

于 2012-09-18T14:52:22.753 回答