我正在阅读Stroustrup的“ C ++编程语言”,他说在两种向变量添加内容的方法中
x = x + a;
和
x += a;
他喜欢,+=
因为它很可能会更好地实施。我认为他的意思是它的运行速度也更快。
但是真的吗?如果它取决于编译器和其他因素,如何检查?
我正在阅读Stroustrup的“ C ++编程语言”,他说在两种向变量添加内容的方法中
x = x + a;
和
x += a;
他喜欢,+=
因为它很可能会更好地实施。我认为他的意思是它的运行速度也更快。
但是真的吗?如果它取决于编译器和其他因素,如何检查?
Answers:
任何编译器称职将产生完全相同的两个构建任何内置类型(同样的机器语言程序int
,float
等等),只要声明确实是那样简单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鼓励使用时的想法+=
。
expr
到var
是var+=expr
,写它的其他方式将读者糊涂。
*f() = *f() + a;
,则可能不妨认真研究一下您真正想要实现的目标……
1
是一个常数,a
可以是易失性,用户定义的类型或其他类型。完全不同。实际上,我什至不知道这是怎么结束的。
您可以通过查看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 +=
,这取决于它们各自的实现。
a
为1,x
则volatile
编译器可以生成inc DWORD PTR [x]
。太慢了
operator +
任何操作,并operator +=
计算第100000个素数,然后返回。当然,这将是一件愚蠢的事情,但这是可能的。
++x
和之间的性能差异很敏感temp = x + 1; x = temp;
,那么很可能应该用汇编而不是c ++来编写...
之间的区别x = x + a
,并x += a
为机器要经过的工作量-一些编译器可能(通常)优化它拿走,但通常情况下,如果我们忽视优化了一会儿,什么情况是,在前者的代码片段中,机器必须对值进行x
两次查找,而在后一种情况下,该查找只需进行一次。
但是,正如我提到的那样,当今大多数编译器都足够智能,可以分析指令并减少所需的最终机器指令。
PS:关于堆栈溢出的第一个答案!
如果您说+=
您正在使编译器的工作变得容易得多。为了使编译器认识到x = x+a
与相同x += a
,编译器必须
分析左侧(x
),以确保它没有副作用,并且始终引用相同的L值。例如,它可以是z[i]
,并且必须确保z
和i
不变。
分析右手边(x+a
)并确保它是一个求和,并且左手边在右手边发生一次且仅发生一次,即使它可以进行变换(如)z[i] = a + *(z+2*0+i)
。
如果你的意思是添加a
到x
,编译器作者赞赏它,当你刚刚说你的意思。这样,您就不会行使编译器的编写者希望他/她摆脱了所有错误的那一部分,并且这实际上并不会使您的生活更轻松,除非您坦白地说无法直截了当Fortran模式。
举一个具体的例子,想象一个简单的复数类型:
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的情况,它必须制作一个额外的临时变量,然后将其复制。
您问错了问题。
这不太可能提高应用程序或功能的性能。即使是这样,找出问题的方法是分析代码并确定它对您的影响。与其在此级别上担心哪个速度更快,不如从清晰度,正确性和可读性方面考虑更为重要。
当您考虑到即使这是重要的性能因素时,编译器也会随着时间的推移而发展,这一点尤其正确。有人可能会想出一种新的优化方法,今天正确的答案明天可能会出错。这是过早优化的经典案例。
这并不是说性能根本不重要……只是实现您的绩效目标是错误的方法。正确的方法是使用分析工具来了解代码实际在哪里花费时间,从而在哪里集中精力。