之间是否存在性能差异 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);

}

文件是相同的,但<=>和<=>:

除外
$ 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

其他提示

来自Andrew Koenig的效率与意图

  

首先,++ii++更有效,至少在涉及整数变量的情况下,这是显而易见的。

并且:

  

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

因此,如果未使用结果值,我将使用<=>。但不是因为它更有效:因为它正确地表明了我的意图。

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

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

但是,如果i++是复杂类型,那么您可能会发现可衡量的差异。对于++it,您必须在增加课程之前复制课程。根据副本中涉及的内容,它确实可能会更慢,因为<=>您可以返回最终值。

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

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

可能发生这种情况的真实例子是使用迭代器。复制迭代器不太可能成为你应用程序的瓶颈,但是习惯使用<=>而不是<=>而不会影响结果仍然是一个好习惯。

从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. 1添加到i
  2. 比较100是否小于Z==0
  3. 如果<=>小于<=>,则为条件分支。
  4. 而递减循环:

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

    循环将包含以下各项的说明:

    1. 递减<=>,设置CPU寄存器状态标志。
    2. 条件分支取决于CPU寄存器状态(<=>)。
    3. 当然,只有在递减到零时才有效!

      从ARM System Developer's Guide中记得。

简短回答:

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

长答案:

其他所有答案都没有提到的是,之间的区别 ++i 相对 i++ 仅在找到的表达式内才有意义。

如果是 for(i=0; i<n; i++), , 这 i++ 在它自己的表达中是单独的:之前有一个序列点 i++ 其后还有一个。因此生成的唯一机器代码是“增加 i 经过 1”并且它与程序的其余部分的顺序是明确定义的。所以如果你将其更改为前缀 ++, ,一点也不重要,你仍然会得到机器代码“increase i 经过 1".

之间的差异 ++ii++ 仅在诸如以下的表达式中重要 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中是可以忽略的。


详情。

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

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

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

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

2。鲜为人知的事实:<=> 可能更快

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

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

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

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

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

如果你的代码中没有一个非常愚蠢的运算符实现:

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

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

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

我可以想到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)。因此,如果它们是某些表达式的一部分,则比较它们并不是真正有效的。通常,如果在表达式中使用a = b++ + 1;,则不能使用a = ++b;,因此即使<=>可能更有效,也只会出错。当然,例外情况是表达式要求它(例如<=>可以改为<=>)。

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

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

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

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

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

myArray[i++] = "hello";

VS

myArray[++i] = "hello";

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

只是我的两分钱。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top