提问



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

最佳参考


执行摘要:不。


i++可能比++i慢,因为i的旧值
可能需要保存以供以后使用,但在实践中都是现代的
编译器将优化这一点。


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


$ cat i++.c
extern void g(int i);
void f()
{
    int i;

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

}


文件是相同的,++ii++除外:


$ 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

其它参考1


从安德鲁科尼希的效率与意图:[97]



??首先,至少在涉及整数变量的情况下,++ii++更有效是很明显的。



而且:



??因此,人们应该问的问题不是这两个操作中哪一个更快,而是这两个操作中的哪一个更准确地表达了您要完成的任务。我提出,如果你没有使用表达式的值,就没有理由使用i++而不是++i,因为没有理由复制变量的值,增加变量,然后扔掉副本。



所以,如果没有使用结果值,我会使用++i。但不是因为它更有效:因为它正确地说明了我的意图。

其它参考2


一个更好的答案是++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++来保持结果不受影响仍然是一种好习惯。

其它参考3


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


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


在每个循环中,您将分别获得一条指令:



  1. 1添加到i

  2. 比较i是否小于100

  3. 如果i小于100,则为条件分支。



而递减循环:


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


循环将为每个:



  1. 递减i,设置CPU寄存器状态标志。

  2. 条件分支取决于CPU寄存器状态(Z==0)。



当然这只有在递减到零时才有效!


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

其它参考4


摘自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时需要一个临时对象。


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


但正如你指出的那样,由于可以进行编译器优化,实际上没有区别。

其它参考5


简答:


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


答案很长:


每个其他答案都没有提到的是++ii++之间的区别仅在其找到的表达式中有意义。


for(i=0; i的情况下,i++在它自己的表达式中是单独的:在i++之前有一个序列点,并且在它之后有一个序列点。因此,生成的唯一机器代码是i增加1,并且明确定义了如何根据程序的其余部分对其进行排序。因此,如果您将其更改为++前缀,那么最重要的是,您仍然可以通过1获得机器代码增加i


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


因此,您可以假设array[i++] = x;被转换为机器代码:



  • i的值存储在寄存器A中。

  • 将数组的地址存储在寄存器B中。

  • 添加A和B,将结果存储在A。

  • 在由A表示的新地址处,存储x。
  • 的值
  • i在寄存器A中的存储值//效率低,因为这里有额外的指令,我们已经做了一次。

  • 递增寄存器A。

  • i中存储寄存器A.



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



  • i的值存储在寄存器A中。

  • 将数组的地址存储在寄存器B中。

  • 添加A和B,将结果存储在B。

  • 递增寄存器A。

  • i中存储寄存器A.

  • ...//代码的其余部分。



仅仅因为你作为C程序员被训练认为后缀++发生在最后,机器代码不必以这种方式排序。


因此,C中的前缀和后缀++之间没有区别。现在你作为一个C程序员应该是多变的,是在某些情况下不一致使用前缀而在其他情况下使用后缀的人,没有任何理由。这表明他们不确定C是如何工作的,或者他们对语言有不正确的认识。这总是一个不好的迹象,它反过来表明他们在他们的计划中做出其他可疑的决定,基于迷信或宗教教条。


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

其它参考6


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


使用对读取代码最有意义的人。

其它参考7


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





细节。


1。众所周知的C ++问题:++i更快



在C ++中,++i更有效iff i是某种具有重载增量运算符的对象。


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


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


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



如果不需要调用构造函数/析构函数,这在C中总是如此,++ii++应该同样快,对吧?不是。它们几乎同样快速,但可能存在细微差别,大多数其他答案者都采用了错误的方法。


i++如何更快?

重点是数据依赖性。如果需要从内存加载该值,则需要对其进行两次后续操作,递增并使用它。使用++i时,需要在之前完成增量可以使用该值。使用i++时,使用不依赖于增量,并且CPU可以并行执行使用操作 到增量操作。差异最多只有一个CPU周期,所以它确实是可以忽略不计的,但它确实存在。而这是许多人所期望的另一种方式。

其它参考8


@标记
即使允许编译器优化掉(基于堆栈)变量的临时副本,gcc(在最近的版本中)也是如此,
并不意味着所有编译器都会这样做。


我刚刚使用我们当前项目中使用的编译器对其进行了测试,其中3个中没有对其进行优化。


永远不要假设编译器正确,特别是如果可能更快,但从不慢的代码易于阅读。


如果你的代码中没有一个操作符的真正愚蠢的实现:


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

其它参考9


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


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

其它参考10


我可以想到postfix比前缀增量慢的情况:


想象一下,带寄存器A的处理器用作累加器,它是许多指令中使用的唯一寄存器(一些小型微控制器实际上就是这样)。


现在想象下面的程序和它们转换成一个假设的程序集:


前缀增量:


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]


后缀增量:


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]


注意如何强制重新加载b的值。使用前缀增量,编译器可以只增加该值并继续使用它,可能避免重新加载它,因为在增量之后所需的值已经在寄存器中。但是,使用postfix增量时,编译器必须处理两个值,一个是旧值,一个是递增值,如上所示,这会导致一次内存访问。


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





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

其它参考11


我总是喜欢预增量,但是......


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


因此,出于实际目的,两种形式的表现之间可能没有太大区别。但是,我总是喜欢预增量,因为直接表达我想说的内容似乎更好,而不是依赖于优化者搞清楚。


此外,减少optmizer可能意味着编译器运行得更快。

其它参考12


我的C有点生疏,所以我提前道歉。 Speedwise,我可以理解结果。但是,我很困惑这两个文件是如何出现在同一个MD5哈希中的。也许for循环运行相同,但是以下两行代码不会产生不同的汇编?


myArray[i++] = "hello";


VS


myArray[++i] = "hello";


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


只是我的两分钱。