有没有之间的性能差异i++
而++i
如果结果不能用?
执行摘要:不.
i++
++i
因为旧的值i
可能需要保存以供以后使用,所以可能比它慢,但实际上所有现代编译器都会优化它.
我们可以通过查看此函数的代码来证明这一点,包括++i
和i++
.
$ cat i++.c extern void g(int i); void f() { int i; for (i = 0; i < 100; i++) g(i); }
文件是相同的,除了++i
和i++
:
$ diff i++.c ++i.c 6c6 < for (i = 0; i < 100; i++) --- > for (i = 0; i < 100; ++i)
我们将编译它们,并获得生成的汇编程序:
$ gcc -c i++.c ++i.c $ gcc -S i++.c ++i.c
我们可以看到生成的对象和汇编程序文件都是相同的.
$ md5 i++.s ++i.s MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e $ md5 *.o MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22 MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
从安德鲁科尼希的效率与意图:
首先,至少在涉及整数变量的情况下,
++i
效率远非显而易见i++
.
而且:
因此,人们应该问的问题不是这两个操作中哪一个更快,而是这两个操作中的哪一个更准确地表达了您要完成的任务.我提交说,如果你没有使用表达式的值,那么就没有理由使用
i++
而不是++i
,因为没有理由复制变量的值,增加变量,然后抛弃副本.
所以,如果没有使用结果值,我会使用++i
.但不是因为它更有效:因为它正确地表明了我的意图.
一个更好的答案是++i
有时会更快但从不慢.
每个人似乎都认为这i
是一个常规的内置类型,如int
.在这种情况下,将没有可衡量的差异.
但是,如果i
是复杂类型,那么您可能会发现可测量的差异.因为i++
你必须在增加它之前复制你的课程.根据副本中涉及的内容,它确实可能会变慢,因为++it
您可以返回最终值.
Foo Foo::operator++() { Foo oldFoo = *this; // copy existing value - could be slow // yadda yadda, do increment return oldFoo; }
另一个区别是,++i
您可以选择返回引用而不是值.同样,根据制作对象副本所涉及的内容,这可能会更慢.
可能发生这种情况的一个真实示例是迭代器的使用.复制迭代器不太可能成为你应用程序的瓶颈,但是习惯使用++i
而不是i++
不影响结果的习惯仍然是一种好习惯.
摘自Scott Meyers,更有效的c ++ 项目6:区分增量和减量操作的前缀和后缀形式.
对于对象,前缀版本总是比postfix更受欢迎,特别是在迭代器方面.
如果你看一下运营商的呼叫模式,原因就在于此.
// Prefix Integer& Integer::operator++() { *this += 1; return *this; } // Postfix const Integer Integer::operator++(int) { Integer oldValue = *this; ++(*this); return oldValue; }
看一下这个例子,很容易看出前缀运算符总是比后缀更高效.因为在使用postfix时需要一个临时对象.
这就是为什么当您看到使用迭代器的示例时,它们总是使用前缀版本.
但正如你指出的那样,由于可能发生的编译器优化,实际上没有区别.
如果你担心微观优化,这是另外一个观察.递减循环"可能"比递增循环更有效(取决于指令集架构,例如ARM),给定:
for (i = 0; i < 100; i++)
在每个循环中,您将分别获得一条指令:
添加1
到i
.
比较是否i
小于a 100
.
条件分支if i
小于a 100
.
而递减循环:
for (i = 100; i != 0; i--)
循环将为每个:
递减i
,设置CPU寄存器状态标志.
条件分支取决于CPU寄存器状态(Z==0
).
当然这只有在递减到零时才有效!
记得ARM系统开发人员指南.
简短回答:
在速度i++
和++i
速度方面从来没有任何区别.一个好的编译器不应该在这两种情况下生成不同的代码.
答案很长:
每个其他答案都没有提到的是,++i
对比之间的区别i++
仅在它所找到的表达式中有意义.
在这种情况下for(i=0; i
i++
单独使用它自己的表达式:在它之前有一个序列点,i++
并且在它之后有一个序列点.如此产生的唯一的机器代码是"增加i
由1
",它是良好定义这是如何相对于测序,以该程序的其余部分.所以,如果你要改变它的前缀++
,它不会有丝毫的关系,你仍然只得到本机代码"增加i
的1
".
之间的差异++i
和i++
唯一的事项表述如array[i++] = x;
对array[++i] = x;
.有些人可能会争辩说并且在这种操作中后缀会更慢,因为i
稍后必须重新加载驻留的寄存器.但请注意,编译器可以自由地以任何方式订购您的指令,只要它不会像C标准所说的那样"破坏抽象机器的行为".
因此,虽然您可能认为它array[i++] = x;
被转换为机器代码:
i
寄存器A的存储值
在寄存器B中存储数组的地址.
添加A和B,将结果存储在A.
在由A表示的这个新地址处,存储x的值.
i
寄存器A中的存储值//效率低,因为这里有额外的指令,我们已经做了一次.
增量寄存器A.
将寄存器A存入i
.
编译器也可以更有效地生成代码,例如:
i
寄存器A的存储值
在寄存器B中存储数组的地址.
添加A和B,将结果存储在B.
增量寄存器A.
将寄存器A存入i
.
... //代码的其余部分.
仅仅因为你作为一名C程序员被训练认为后缀++
发生在最后,机器代码不必以这种方式排序.
所以++
C中的前缀和后缀之间没有区别.现在你作为一个C程序员应该是多变的,是在某些情况下不一致使用前缀的人和其他情况下的后缀,没有任何理由.这表明他们不确定C是如何工作的,或者他们对语言的认识不正确.这总是一个不好的迹象,它反过来表明他们在他们的计划中做出其他可疑的决定,基于迷信或"宗教教条".
"前缀++
总是更快"确实是一个在未来的C程序员中常见的错误教条.
请不要让"哪一个更快"的问题成为决定使用哪个因素.你可能永远不会那么在意,而且程序员的阅读时间比机器时间要贵得多.
使用对读取代码最有意义的人.
首先:之间的区别i++
,并++i
在C. neglegible
细节.
++i
更快在C++中,++i
iff i
是一种具有重载增量运算符的某种对象,效率更高.
为什么?
在++i
,对象首先递增,并且随后可以作为const引用传递给任何其他函数.如果表达式是foo(i++)
因为现在需要在foo()
调用之前完成增量,但是需要传递旧值,则这是不可能的foo()
.因此,编译器i
在执行原始增量运算符之前必须复制.额外的构造函数/析构函数调用是不好的部分.
如上所述,这不适用于基本类型.
i++
可能会更快如果不需要调用构造函数/析构函数,这在C中总是如此,++i
并且i++
应该同样快,对吧?不.它们几乎同样快速,但可能存在细微差别,大多数其他答案者都错误地解决了这个问题.
怎么可能i++
更快?
关键是数据依赖性.如果需要从内存加载该值,则需要对其进行两次后续操作,递增并使用它.使用时++i
,需要在可以使用值之前完成增量.因此i++
,使用不依赖于增量,并且CPU可以与增量操作并行地执行使用操作.差异最多只有一个CPU周期,所以它确实是可以忽略不计的,但它确实存在.而这是许多人所期望的另一种方式.
@Mark尽管允许编译器优化掉(基于堆栈)变量的临时副本,而gcc(在最近的版本中)这样做,并不意味着所有编译器都会这样做.
我刚刚使用我们当前项目中使用的编译器对其进行了测试,其中3个中没有对其进行优化.
永远不要假设编译器正确,特别是如果可能更快但从不慢的代码易于阅读.
如果您的代码中没有其中一个运算符的非常愚蠢的实现:
Alwas比i ++更喜欢++ i.
在C中,如果结果未使用,编译器通常可以将它们优化为相同.
但是,在C++中如果使用提供自己的++运算符的其他类型,前缀版本可能比后缀版本更快.因此,如果您不需要后缀语义,最好使用前缀运算符.