我正在阅读 Stroustrup 的《C++ 程序设计语言》,他提到了两种将值加到变量中的方式。
x = x + a;
和
x += a;
他更喜欢使用+=
,因为它很可能被更好地实现了。我想他的意思是这样做速度更快。
但是真的吗?如果它取决于编译器和其他因素,我该如何检查?
我正在阅读 Stroustrup 的《C++ 程序设计语言》,他提到了两种将值加到变量中的方式。
x = x + a;
和
x += a;
他更喜欢使用+=
,因为它很可能被更好地实现了。我想他的意思是这样做速度更快。
但是真的吗?如果它取决于编译器和其他因素,我该如何检查?
只要语句真的和 x = x + a;
一样简单并且启用了优化,任何值得一用的编译器都会为内置类型(int
,float
等)生成完全相同的机器语言序列。 (值得注意的是,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在鼓励使用+=
时所考虑的问题。
expr
加到 var
的惯用法是var+=expr
,反其道而行之会使读者感到困惑。 - Tadeusz Kopec for Ukraine*f() = *f() + a;
这样的代码,你可能需要好好审视一下自己真正想要实现什么... - Adam Davis通过查看反汇编结果,你可以进行检查,结果会是相同的。
对于基本类型,两者速度是一样的。
这是由调试版本生成的输出(即没有进行优化):
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 +=
,那么就取决于它们各自的实现。
a
是1且 x
是 volatile
,编译器可能会生成 inc DWORD PTR [x]
。这很慢。 - Jamesoperator +
使其无效,然后让 operator +=
计算第 100000 个质数并返回结果。当然,这样做很蠢,但这是可能的。 - Luchian Grigore++x
和 temp = x + 1; x = temp;
的性能差异,那么很可能应该用汇编语言而不是 C++ 编写... - EmirCalabuchx = x + a
和 x += a
的区别在于机器所需的工作量——一些编译器可以(而且通常会)将其优化掉,但通常情况下,如果我们暂时忽略优化,前者的代码片段中,机器需要查找 x
的值两次,而后者只需要进行一次查找。是的!对于人类来说,使用简写更加快速,便于阅读和理解,特别是在变量 x
可能会产生副作用的情况下。因此,总体上对于人类而言更为迅速。一般来说,人类时间的成本远高于计算机时间,所以这必定是你所询问的问题。对吧?
这真的取决于x和a的类型以及+的实现方式。对于
T x, a;
....
x = x + a;
对于x += a,它不需要临时变量。
对于简单类型,没有区别。
+=
,将使编译器的工作变得更加容易。为了让编译器认识到x = x+a
和x += a
是相同的,编译器必须:
分析左边表达式(x
),确保它没有副作用并始终引用同一l-value。例如,它可以是z[i]
,必须确保z
和i
都没有改变。
分析右边表达式(x+a
),确保它是一个求和,并且左边的表达式只出现一次,即使它可以被转换,例如z[i] = a + *(z+2*0+i)
。
a
加到x
上,那么编译器作者会感激你直截了当地表达你的意思。
这样,你就不会让编译器执行它希望消除所有错误的部分了,而这也不会让你的工作变得更容易,除非你无法脱离Fortran模式。x
是POD,则它不会真正产生太大的影响。但是,如果x
是一个类,则可能对operator +
和operator +=
方法进行重载,这可能会导致非常不同的执行时间。举个具体的例子,想象一个简单的复数类型:
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 的情况,需要额外创建一个临时变量,并将其复制。
我认为这应该取决于机器及其架构。如果其架构允许间接内存寻址,编译器可能会使用以下代码来进行优化:
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,在这两个语句(如果它们是整数)上产生相同的代码。