我正在阅读 Stroustrup 的“The C++ Programming Language”,他说有两种方法可以向变量添加内容
x = x + a;
和
x += a;
他更喜欢+=
它,因为它很可能得到更好的实施。我认为他的意思是它也工作得更快。
但真的吗?如果它取决于编译器和其他东西,我该如何检查?
我正在阅读 Stroustrup 的“The C++ Programming Language”,他说有两种方法可以向变量添加内容
x = x + a;
和
x += a;
他更喜欢+=
它,因为它很可能得到更好的实施。我认为他的意思是它也工作得更快。
但真的吗?如果它取决于编译器和其他东西,我该如何检查?
只要语句真的很简单并且启用了优化,任何称职的编译器都会为任何内置类型( , 等)的两种构造生成完全相同的机器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 在鼓励使用+=
.
您可以通过查看拆卸进行检查,这将是相同的。
对于基本类型,两者都同样快。
这是由调试构建生成的输出(即没有优化):
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 +=
,这取决于它们各自的实现。
是的!写起来快,读起来快,想出来也快,因为后者在x
可能有副作用的情况下。因此,对于人类来说,它总体上更快。人力时间通常比计算机时间花费更多,所以这一定是您要问的。对?
x = x + a
和之间的区别在于x += a
机器必须经过的工作量 - 一些编译器可能(并且通常会)优化它,但通常,如果我们暂时忽略优化,会发生在前一个代码片段中,机器必须查找该值x
两次,而在后一次中,此查找只需要发生一次。
然而,正如我所提到的,今天大多数编译器都足够智能,可以分析指令并减少所需的机器指令。
PS:堆栈溢出的第一个答案!
这实际上取决于 x 和 a 的类型以及 + 的实现。为了
T x, a;
....
x = x + a;
编译器必须创建一个临时 T 来包含 x + a 的值,同时对其进行评估,然后将其分配给 x。(在此操作期间它不能使用 x 或 a 作为工作区)。
对于 x += a,它不需要临时的。
对于平凡的类型,没有区别。
如果你说+=
你让编译器的生活变得更轻松。为了让编译器识别它x = x+a
与 相同x += a
,编译器必须
分析左侧 ( x
) 以确保它没有副作用并且始终引用相同的 l 值。例如,它可能是z[i]
,并且它必须确保两者z
都i
不会改变。
分析右手x+a
边z[i] = a + *(z+2*0+i)
(
如果您的意思是添加a
到x
,那么当您说出您的意思时,编译器作者会很感激。这样一来,您就不会使用编译器的编写者希望他/她解决所有错误的部分,这实际上并没有让您的生活变得更轻松,除非您真的无法摆脱困境Fortran 模式。
由于您已标记此 C++,因此无法从您发布的两个语句中知道。您需要知道“x”是什么(有点像答案“42”)。如果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
还应考虑其他复杂情况,例如 if 是返回指针的函数等。大多数生产级编译器,包括 GCC,为这两个语句生成相同的代码(如果它们是整数)。
不,两种方式的处理方式相同。