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

时间:2020-03-05 18:42:33  来源:igfitidea点击:

如果不使用结果值,则" 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

回答

我的C有点生锈,所以我先向我们道歉。从速度上看,我可以理解结果。但是,我对这两个文件如何以相同的MD5哈希输出感到困惑。也许for循环运行相同,但是以下两行代码不会生成不同的程序集吗?

myArray[i++] = "hello";

myArray[++i] = "hello";

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

只是我的两分钱。

回答

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

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

回答

摘自Scott Meyers的《更有效的c ++项目6》:区分增量和减量运算的前缀和后缀形式。

在对象方面,尤其是在迭代器方面,始终优先使用前缀版本而不是后缀。

如果查看运营商的呼叫方式,则其原因是。

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

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

看这个例子,很容易看出前缀运算符将总是比后缀更有效率。由于在使用postfix时需要一个临时对象。

这就是为什么当我们看到使用迭代器的示例时,它们始终使用前缀版本的原因。

但是正如我们为int指出的那样,由于可以进行编译器优化,因此实际上没有区别。

回答

更好的答案是," ++ 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 ++的习惯仍然是一种好习惯。

回答

如果我们担心微优化,这是另外一个观察结果。给定以下条件,减少循环可能比增加循环更有效率(取决于指令集架构,例如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系统开发人员指南》中记住。

回答

从效率与意图(作者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"。但这不是因为它更有效:因为它正确地表达了我的意图。

回答

请不要让"哪个更快"的问题成为使用哪个的决定因素。我们永远都不会在乎那么多,此外,程序员的阅读时间比机器学习的时间要昂贵得多。

使用对人类阅读代码最有意义的方法。

回答

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

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

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

同样,减少优化器的工作量可能意味着编译器运行得更快。

回答

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

我只是使用当前项目中使用的编译器对其进行了测试,而四分之三的代码并未对其进行优化。

永远不要假设编译器正确无误,尤其是在可能更快但永远不会慢的代码易于阅读的情况下。

如果我们在代码中没有真正愚蠢的实现运算符之一,请执行以下操作:

比起i ++,Alwas更喜欢++ i。