C ++中的i ++和++ i之间是否存在性能差异?

Is there a performance difference between i++ and ++i in C?

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


执行摘要:没有。

由于i的旧值,因此i++可能比++i慢。可能需要保存以备日后使用,但实际上所有现代编译器将对此进行优化。

我们可以通过查看此函数的代码来演示这一点,都有++ii++

1
2
3
4
5
6
7
8
9
10
$ cat i++.c
extern void g(int i);
void f()
{
    int i;

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

}

++ii++外,其他文件相同:

1
2
3
4
5
$ diff i++.c ++i.c
6c6
<     for (i = 0; i < 100; i++)
---
>     for (i = 0; i < 100; ++i)

我们将编译它们,并获得生成的汇编程序:

1
2
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c

我们可以看到生成的对象文件和汇编程序文件都是相同的。

1
2
3
4
5
6
7
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22


从Andrew Koenig的效率与意图来看:

First, it is far from obvious that ++i is more efficient than i++, at least where integer variables are concerned.

以及:

So the question one should be asking is not which of these two operations is faster, it is which of these two operations expresses more accurately what you are trying to accomplish. I submit that if you are not using the value of the expression, there is never a reason to use i++ instead of ++i, because there is never a reason to copy the value of a variable, increment the variable, and then throw the copy away.

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


一个更好的答案是,++i有时会更快,但从不慢。

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

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

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

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

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


如果你担心微观优化,这里有一个额外的观察。递减循环"可能"比递增循环更有效(取决于指令集体系结构,如ARM),前提是:

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

在每个循环上,您将有一条指令,分别用于:

  • i中加入1
  • 比较i是否小于100
  • 如果i小于100的条件分支。
  • 而递减循环:

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

    循环将为以下各项提供指令:

  • 减少i,设置CPU寄存器状态标志。
  • 一种取决于CPU寄存器状态的条件分支(Z==0)。
  • 当然,这只在递减为零时才有效!

    从ARM系统开发人员指南中记起。


    从Scott Meyers中获取叶,更有效的C++项目6:区分前缀和后缀形式的增量和减量运算。

    在对象方面,尤其是在迭代器方面,前缀版本总是优于后缀版本。

    如果您查看操作符的调用模式,那么会出现这种情况的原因。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Prefix
    Integer& Integer::operator++()
    {
        *this += 1;
        return *this;
    }

    // Postfix
    const Integer Integer::operator++(int)
    {
        Integer oldValue = *this;
        ++(*this);
        return oldValue;
    }

    看看这个例子,很容易看出前缀操作符总是比后缀更有效。因为使用后缀时需要临时对象。

    这就是为什么当您看到使用迭代器的示例时,它们总是使用前缀版本。

    但是正如您所指出的,对于int,实际上没有什么区别,因为编译器的优化可以发生。


    简短回答:

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

    长回答:

    其他所有答案都没有提到的是,++ii++之间的差异仅在所发现的表达式中有意义。

    for(i=0; i的情况下,i++在它自己的表达式中是单独的:在i++之前有一个序列点,在它之后有一个序列点。因此,生成的唯一机器代码是"由1增加i",并且它很好地定义了与程序其余部分相关的顺序。所以,如果你把它改成前缀++,一点也不重要,你只需要得到机器代码"增加i"加上1

    ++ii++之间的差异仅在array[i++] = x;array[++i] = x;等表达中起作用。有些人可能会争辩说,在这样的操作中,后缀会变慢,因为i所在的寄存器必须稍后重新加载。但是,请注意,编译器可以自由地以任何方式对您的指令进行排序,只要它不象C标准所说的那样"破坏抽象机的行为"。

    因此,虽然您可以假定array[i++] = x;被翻译为机器代码,但是:

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

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

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

    仅仅因为你作为一个C程序员被训练成认为后缀++发生在末尾,机器代码就不必以这种方式被订购。

    因此,在C语言中前缀和后缀++之间没有区别。现在作为C程序员,你应该有所不同,在某些情况下不一致地使用前缀,而在其他情况下不一致地使用后缀,这是没有任何理由的。这表明他们不确定C是如何工作的,或者他们对语言的了解不正确。这始终是一个坏迹象,反过来也表明他们在他们的计划中,基于迷信或"宗教教条"做出了其他有问题的决定。

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


    请不要让"哪一个更快"的问题成为决定使用哪一个的因素。很可能你永远不会那么在意,而且,程序员阅读时间比机器时间要贵得多。

    使用对人类来说最有意义的代码。


    首先:i++++i之间的区别在c中是可以忽略的。

    到细节。

    1.众所周知的C++问题:EDCOX1〔1〕更快。

    在C++中,EDOCX1×1是更有效的IFF EDCOX1,4是一种具有过载增量运算符的对象。

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

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

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

    如果不需要调用构造函数/析构函数(在C中总是如此),那么++ii++应该同样快,对吗?不,他们的速度几乎一样快,但可能存在一些小的差异,大多数其他回答者都是错误的。

    i++如何更快?关键是数据依赖性。如果需要从内存中加载该值,则需要对其执行两个后续操作,分别递增和使用该值。使用++i时,需要先进行递增,然后才能使用该值。使用i++时,使用不依赖于增量,CPU可以与增量操作并行执行使用操作。这种差异至多只有一个CPU周期,所以它确实是可忽略的,但它确实存在。这与许多人所期望的相反。


    @标记即使编译器可以优化掉变量的临时副本(基于堆栈),而gcc(在最新版本中)正在这样做,并不意味着所有编译器都会这样做。

    我刚刚用我们在当前项目中使用的编译器测试了它,4个编译器中有3个没有优化它。

    永远不要假设编译器能够正确地完成任务,特别是如果代码可能更快,但速度永远不会变慢,那么就很容易阅读。

    如果代码中没有一个真正愚蠢的运算符实现:

    Alwas更喜欢++I而不是I++。


    在C语言中,如果结果未被使用,编译器通常可以将它们优化为相同的。

    但是,在C++中,如果使用提供自己的++操作符的其他类型,前缀版本可能会比后缀版本快。因此,如果不需要后缀语义,最好使用前缀操作符。


    我可以想到一种情况,即后缀比前缀增量慢:

    假设一个寄存器为A的处理器被用作累加器,它是许多指令中使用的唯一寄存器(一些小型微控制器实际上是这样)。

    现在想象一下下面的程序及其翻译成一个假设的程序集:

    前缀增量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    a = ++b + c;

    ; increment b
    LD    A, [&b]
    INC   A
    ST    A, [&b]

    ; add with c
    ADD   A, [&c]

    ; store in a
    ST    A, [&a]

    后缀增量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    a = b++ + c;

    ; load b
    LD    A, [&b]

    ; add with c
    ADD   A, [&c]

    ; store in a
    ST    A, [&a]

    ; increment b
    LD    A, [&b]
    INC   A
    ST    A, [&b]

    注意如何强制重新加载EDOCX1[2]的值。使用前缀增量,编译器可以增加值并继续使用它,可能避免重新加载它,因为所需的值已经在增量之后的寄存器中。但是,对于后缀增量,编译器必须处理两个值,一个是旧值,另一个是增量值,正如我上面所示,这将导致更多的内存访问。

    当然,如果不使用增量值,例如单个i++;语句,编译器可以(并且确实)生成一条增量指令,而不管后缀或前缀的用法如何。

    作为旁注,我想说的是,有一个b++的表达式不能简单地转换为有++b的表达式,而不需要任何额外的努力(例如,通过添加- 1)。因此,如果这两个表达式是某个表达式的一部分,则对它们进行比较并不真正有效。通常,在表达式中使用b++时,不能使用++b,因此即使++b可能更有效,它也只是错误的。当然,如果表达式需要例外(例如,a = b++ + 1;可以更改为a = ++b;)。


    不过,我总是喜欢预增量…

    我想指出的是,即使在调用operator++函数的情况下,如果函数是内联的,编译器也能够优化临时函数。由于operator++通常很短,并且常常在头中实现,因此它很可能是内联的。

    因此,出于实际目的,这两种形式的性能可能没有太大差别。但是,我总是更喜欢预增量,因为它似乎更好地直接表达我想说的内容,而不是依赖于优化器来解决。

    另外,给optmizer更少的操作可能意味着编译器运行得更快。


    我的C有点生锈,所以我提前道歉。很快,我就能理解结果。但是,对于这两个文件是如何输出到同一个MD5哈希的,我感到困惑。也许for循环运行相同,但以下两行代码不会生成不同的程序集吗?

    1
    myArray[i++] ="hello";

    VS

    1
    myArray[++i] ="hello";

    第一个将值写入数组,然后递增i。第二个将值写入数组。我不是汇编专家,但我不知道这两行代码如何生成相同的可执行文件。

    就我的两分钱。