自增:x++与x += 1的区别

13

我听说很多开发者使用 x += 1 而不是 x++ 来提高代码的清晰度。我知道对于新手来说 x++ 可能会有歧义,而 x += 1 总是更清晰明了,但是在这两种方法之间是否存在效率上的差异呢?

使用 for 循环的示例:

for(x = 0; x < 1000; x += 1) vs for(x = 0; x < 1000; x++)

我知道通常情况下并不会产生太大影响,但如果我反复调用这种循环的函数,长期累积下来可能会有所增加。

另一个例子:

while(x < 1000) {
    someArray[x];
    x += 1;
}

对比

while(x < 1000) {
    someArray[x++];
}

可以将x++替换为x += 1而不会有性能损失吗?我特别关注第二个例子,因为我使用了两行代码而不是一行。

如果在一个大循环中进行递增数组项,someArray[i]++someArray[i] += 1更快吗?


7
你可以通过运行基准测试来找出答案!无论你使用哪种编程语言,编译器很可能会为两个语句生成相同的代码。 “过早优化是万恶之源。” - Felix Kling
2
为了后人的缘故,我更喜欢使用 x++ 而不是 x += 1 - Jacob Eggers
2
@YGomez:不,x++ 首先返回 x,然后再将其增加。 - Felix Kling
我读过很多开发者为了清晰起见使用 x += 1 而不是 x++。但这种“清晰度”与使用 if ( boolean == true ) 条件语句或 int i = 0; while( i < max ) { /* body */ i++; } 循环,或为每个计算值使用命名临时变量的方式完全相同。- {{需要引证}} - user719662
@tjameson 实际上,如果您保持正确的缩进,使用括号并根据需要将其拆分成行,则格式正确的嵌套三元运算符是*绝对可读的。您的示例已损坏,因为您有不匹配的对。正确编写的代码只需简单地写成 x ? ( y ? a : b ) : ( z ? c : d ); ... 这几乎与双重if一样易读或清晰。 - user719662
显示剩余10条评论
5个回答

23
任何一个正常或疯狂的编译器都会为两者生成相同的机器代码。

1
对于解释性语言呢? - beatgammit
18
如果你正在使用解释型语言,那么你会面临更大的性能问题。 - SLaks
我认为这就回答了手头的问题。我原以为可能是这样,但我只是想确保一下。 - beatgammit
@tjameson:除此之外,像PyPy和V8这样的JIT编译解释器被期望处理这样的优化,它们真的很容易实现(PyPy的开发人员优化了堆分配,几个月后我仍然感到惊讶)。 - user395760
@delnan - 哦,这很有趣。这是否意味着他们对开发人员撒谎?我会认为new将数据放在堆栈上而不是堆上,但我想这真的无关紧要,因为地址甚至都无法访问。 - beatgammit
@tjameson:Python 中甚至没有 new - 内存来自于... 好吧,我们甚至不考虑内存来自哪里,更不用说它实际上在哪里了。话虽如此,只有当没有引用从 JITted 的代码片段中逸出时,它才能正常工作,因为通常解释器使用指针。详见 http://morepypy.blogspot.com/2010/09/escape-analysis-in-pypys-jit.html 和 http://morepypy.blogspot.com/2010/09/using-escape-analysis-across-loop.html 以获取详细解释。 - user395760

5
假设您谈论将它们应用于基本类型而不是自己的类,因为它们可能会产生巨大的差异,特别是在打开优化时,它们可以产生相同的输出。令我惊讶的是,在反编译的应用程序中,我经常发现在汇编级别上使用x+=1而不是x++(add vs inc)。

哦,那很有趣。汇编信息加一。 - beatgammit
请注意,据我所知它们是使用Visual Studio编译的,并且我没有看到原始源代码,因此无法确定在源代码中是+=还是++。 - Nobody moving away from SE
@Nobody- 只是好奇,你认为这对gcc也适用吗? - beatgammit
我真的不能确定。我认为这取决于许多因素,如优化和架构设置,但这可能需要由gcc开发人员回答或通过反编译一些准备好的测试用例来解决。 - Nobody moving away from SE
刚刚拼凑了一些小代码。GCC 4.5.0没有优化就使用了add指令。加上O1-3优化选项后,它使用lea指令。 - Nobody moving away from SE

4
任何好的编译器都应该能够识别它们是相同的,所以最终它们之间不应该有性能差异。如果您想自行验证,请进行基准测试。

0

假设你是一个懒惰的编译器实现者,不想在机器代码生成模块中编写优化程序。

x = x + 1;

将被翻译成以下代码:

mov $[x],$ACC
iadd $1,$ACC
mov $ACC,$[x]

而 x++ 会被翻译为:

incr $[x] ;increment by 1

如果一个指令在1个机器周期内执行,那么x = x + 1将需要3个机器周期,而x++只需要1个机器周期。(这里使用的是假想机器)。

但幸运的是,大多数编译器实现者并不懒惰,并且会在机器代码生成模块中编写优化。因此,x = x+1和x++应该需要相等的执行时间。:-P


大多数编译器的作者实际上都很懒,他们从不在中间代码中实现增量指令。相反,他们有一个加法指令,并将这种优化留给后端处理。 - razeh

0

当你说“长期来看可能会累积起来”时,不要那样想。

相反,以百分比的形式思考。当你发现程序计数器在那个精确代码中的时间达到10%或更多时,那就要担心了。 原因是,如果百分比很小,那么你通过改进它可节省的最大值也很小。

如果时间百分比低于10%,那么你几乎肯定在代码的其他部分中有更大的加速机会,通常是可以避免的函数调用形式。

以下是一个例子。


你说的没错,迈克。然而,理解x++和x += 1通常没有区别是有价值的。如果有区别,那么理解为什么要以一种或另一种方式编码就有意义了。考虑Java和C#中的字符串连接和字符串构建器。有些情况下使用其中一个是有意义的,而有些情况下使用另一个是有意义的。经验法则可以帮助您获得更好的代码和性能,但详细分析始终给出最终答案。 - aaaa bbbb
@aaaa:当然,你是对的。在所有那些重要的情况下,它之所以重要是因为它会花费相当大的时间——就像你说的“有意义”。你和我都知道这只是常识,但我仍然惊讶于还有多少人没有学到这一点。 - Mike Dunlavey
是的,没错,但如果我有一个模块,可以批量将RGB转换为BGR(某些图像使用相反的方式,不要问我为什么),那该怎么办呢?这将花费大部分时间在循环中进行一些位移操作。 - beatgammit
@tjameson:那么这是我做的事情,总是有效的。 我会拍几个[stackshots](https://dev59.com/n3RC5IYBdhLWcg3wOOL1#378024)。每个都告诉我它在那一刻正在做什么(更重要的是,为什么要这样做)。 如果两个或两个以上在该代码中,则其百分比足够高,值得优化。 它将直接指向花费时间的指令。 这就是我知道我应该优化它的方式。 简单明了。 请注意,除非它告诉我应该这样做,否则我不会这样做。 这是关键点,因为问题可能出现在其他地方。 - Mike Dunlavey

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