如果不使用结果值,i++和++i之间是否有性能差异?


当前回答

首先:在C中i++和++i之间的差异是可以忽略的。


到细节。

1. 众所周知的c++问题:++i更快

在c++中,如果i是具有重载自增操作符的某种对象,则++i的效率更高。

为什么? 在++i中,对象首先递增,然后可以作为const引用传递给任何其他函数。如果表达式是foo(i++),这是不可能的,因为现在增量需要在foo()调用之前完成,但旧的值需要传递给foo()。因此,编译器被迫在对原始i执行自增操作符之前复制i。额外的构造函数/析构函数调用是不好的部分。

如上所述,这不适用于基本类型。

2. 鲜为人知的事实是:i++可能更快

如果不需要调用构造函数/析构函数,这在C中总是如此,那么++i和i++应该同样快,对吗?不。他们的速度几乎一样快,但可能有一些小的差异,这是大多数其他答案的错误理解。

How can i++ be faster? The point is data dependencies. If the value needs to be loaded from memory, two subsequent operations need to be done with it, incrementing it, and using it. With ++i, the incrementation needs to be done before the value can be used. With i++, the use does not depend on the increment, and the CPU may perform the use operation in parallel to the increment operation. The difference is at most one CPU cycle, so it is really neglegible, but it is there. And it is the other way round then many would expect.

其他回答

摘自Andrew Koenig的《效率与意图》:

首先,++i是否比i++更有效还不明显,至少在涉及整型变量时是这样。

和:

所以人们应该问的问题不是这两种操作中哪一种更快,而是这两种操作中哪一种更准确地表达了你想要完成的事情。我认为,如果你不使用表达式的值,永远没有理由使用i++而不是++ I,因为永远没有理由复制一个变量的值,增加变量,然后扔掉拷贝。

因此,如果没有使用结果值,则使用++ I。但不是因为它更有效,而是因为它正确地表达了我的意图。

简短的回答:

i++和++i在速度上没有任何区别。一个好的编译器不应该在这两种情况下生成不同的代码。

长一点的回答:

其他答案都没有提到的是,++i和i++之间的区别只在它所找到的表达式中有意义。

对于for(i=0;我< n;i++), i++在它自己的表达式中是单独的:在i++之前有一个序列点,在它之后有一个。因此,生成的唯一机器码是“将i增加1”,并且它是如何与程序的其余部分进行排序的。所以如果你把它改成前缀++,这一点关系都没有,你仍然会得到机器代码“将i增加1”。

++i和i++之间的差异只在数组[i++] = x;与数组[++i] = x;有些人可能会争辩说,后缀在这样的操作中会更慢,因为i所在的寄存器稍后必须重新加载。但是请注意,编译器可以自由地以任何它喜欢的方式对你的指令进行排序,只要它不像C标准所说的那样“破坏抽象机器的行为”。

所以当你假设数组[i++] = x;被翻译成机器代码为:

将i的值存储在寄存器A中。 存储寄存器B中数组的地址。 将A和B相加,将结果存储在A中。 在这个由A表示的新地址上,存储x的值。 在寄存器A //中存储i的值是低效的,因为这里有额外的指令,我们已经做过一次了。 增量寄存器A。 在i中存储寄存器A。

编译器也可以更有效地生成代码,例如:

将i的值存储在寄存器A中。 存储寄存器B中数组的地址。 添加A和B,将结果存储在B中。 增量寄存器A。 在i中存储寄存器A。 ... //其余的代码。

只是因为作为一个C程序员,你被训练成认为后缀++发生在结尾,所以机器代码不需要以这种方式排序。

所以在C语言中,前缀++和后缀++没有区别。现在,作为一个C程序员,你应该知道的是,有些人在某些情况下不一致地使用前缀,而在其他情况下不一致地使用后缀,没有任何理由。这表明他们不确定C语言是如何工作的,或者他们对这门语言的了解不正确。这总是一个不好的迹象,它反过来表明他们在他们的程序中做出了其他有问题的决定,基于迷信或“宗教教条”。

“前缀++总是更快”确实是一个错误的教条,在准C程序员中很常见。

如果你担心微观优化,这里有一个额外的观察。递减循环“可能”比递增循环更有效(取决于指令集架构,例如ARM),给定:

for (i = 0; i < 100; i++)

在每个循环中,你将有一个指令:

i加1。 比较i是否小于100。 如果i小于100,则为条件分支。

而递减循环:

for (i = 100; i != 0; i--)

循环将有一个指令用于以下每一个:

递减i,设置CPU寄存器状态标志。 一个依赖于CPU寄存器状态(Z==0)的条件分支。

当然,这只适用于递减到零!

记得ARM系统开发人员指南。

我已经阅读了这里的大部分答案和许多评论,我没有看到任何一个我能想到的实例,在哪里i++比++ I更有效(也许令人惊讶的是- I比I更有效)。这是针对DEC PDP-11的C编译器!

PDP-11的汇编指令用于寄存器的前减和后增,但没有相反的指令。这些指令允许任何“通用”寄存器用作堆栈指针。所以如果你使用像*(i++)这样的东西,它可以被编译成一个汇编指令,而*(++i)不能。

这显然是一个非常深奥的例子,但它确实提供了后增量更有效的例外(或者我应该说曾经,因为现在对PDP-11 C代码的需求并不多)。

更好的答案是++i有时会更快,但绝不会变慢。

每个人似乎都认为i是一个常规的内置类型,比如int。在这种情况下,将没有可测量的差异。

然而,如果i是复型,那么你很可能会发现一个可测量的差异。对于i++,您必须在递增类之前复制它。根据复制中涉及的内容,它确实可能会变慢,因为使用++i可以只返回最终值。

Foo Foo::operator++()
{
  Foo oldFoo = *this; // copy existing value - could be slow
  // yadda yadda, do increment
  return oldFoo;
}

另一个区别是,使用++i,您可以选择返回一个引用而不是一个值。同样,根据复制对象所涉及的内容,这可能会更慢。

在现实世界中,迭代器的使用就是可能发生这种情况的一个例子。复制迭代器不太可能成为应用程序中的瓶颈,但养成使用++i而不是i++的习惯仍然是一个很好的实践,因为i++的结果不会受到影响。