C++ 中的 i++ 和 ++i 之间有性能差异吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24901/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:18:15  来源:igfitidea点击:

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

c++performanceooppost-incrementpre-increment

提问by Mark Harrison

We have the question is there a performance difference between i++and ++iin C?

我们有一个问题,C 中之间是否存在性能差异?i++++i

What's the answer for C++?

C++ 的答案是什么?

回答by Mark Harrison

[Executive Summary: Use ++iif you don't have a specific reason to use i++.]

[执行摘要:++i如果您没有使用的特定理由,请使用i++。]

For C++, the answer is a bit more complicated.

对于 C++,答案有点复杂。

If iis a simple type (not an instance of a C++ class), then the answer given for C ("No there is no performance difference")holds, since the compiler is generating the code.

如果i是简单类型(不是 C++ 类的实例),则为 C 给出的答案(“不存在性能差异”)成立,因为编译器正在生成代码。

However, if iis an instance of a C++ class, then i++and ++iare making calls to one of the operator++functions. Here's a standard pair of these functions:

但是,如果i是 C++ 类的实例,则i++++i正在调用其中一个operator++函数。这是这些函数的标准对:

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

Since the compiler isn't generating code, but just calling an operator++function, there is no way to optimize away the tmpvariable and its associated copy constructor. If the copy constructor is expensive, then this can have a significant performance impact.

由于编译器不生成代码,而只是调用一个operator++函数,因此无法优化掉tmp变量及其关联的复制构造函数。如果复制构造函数很昂贵,那么这会对性能产生重大影响。

回答by wilhelmtell

Yes. There is.

是的。有。

The ++ operator may or may not be defined as a function. For primitive types (int, double, ...) the operators are built in, so the compiler will probably be able to optimize your code. But in the case of an object that defines the ++ operator things are different.

++ 运算符可以定义为函数,也可以不定义为函数。对于原始类型(int、double、...),运算符是内置的,因此编译器可能能够优化您的代码。但是对于定义 ++ 运算符的对象,情况就不同了。

The operator++(int) function must create a copy. That is because postfix ++ is expected to return a different value than what it holds: it must hold its value in a temp variable, increment its value and return the temp. In the case of operator++(), prefix ++, there is no need to create a copy: the object can increment itself and then simply return itself.

operator++(int) 函数必须创建一个副本。那是因为 postfix ++ 应该返回一个与它所保存的值不同的值:它必须将它的值保存在一个临时变量中,增加它的值并返回临时值。在operator++(),前缀++的情况下,不需要创建副本:对象可以自增,然后简单地返回自己。

Here is an illustration of the point:

这是该点的说明:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

Every time you call operator++(int) you must create a copy, and the compiler can't do anything about it. When given the choice, use operator++(); this way you don't save a copy. It might be significant in the case of many increments (large loop?) and/or large objects.

每次调用 operator++(int) 时都必须创建一个副本,编译器对此无能为力。当有选择时,使用 operator++(); 这样你就不会保存副本。在许多增量(大循环?)和/或大对象的情况下,它可能很重要。

回答by Sebastian Mach

Here's a benchmark for the case when increment operators are in different translation units. Compiler with g++ 4.5.

这是增量运算符在不同翻译单元中的情况的基准。用 g++ 4.5 编译。

Ignore the style issues for now

暂时忽略样式问题

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}


O(n) increment

O(n) 增量

Test

测试

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Results

结果

Results (timings are in seconds) with g++ 4.5 on a virtual machine:

在虚拟机上使用 g++ 4.5 的结果(计时以秒为单位):

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82


O(1) increment

O(1) 增量

Test

测试

Let us now take the following file:

现在让我们获取以下文件:

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

It does nothing in the incrementation. This simulates the case when incrementation has constant complexity.

它在增量中什么也不做。这模拟了增量具有恒定复杂性的情况。

Results

结果

Results now vary extremely:

结果现在变化很大:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90


Conclusion

结论

Performance-wise

性能方面

If you do not need the previous value, make it a habit to use pre-increment. Be consistent even with builtin types, you'll get used to it and do not run risk of suffering unecessary performance loss if you ever replace a builtin type with a custom type.

如果不需要之前的值,请养成使用预增量的习惯。即使与内置类型保持一致,你也会习惯它,并且如果你曾经用自定义类型替换内置类型,就不会冒遭受不必要的性能损失的风险。

Semantic-wise

语义明智的

  • i++says increment i, I am interested in the previous value, though.
  • ++isays increment i, I am interested in the current valueor increment i, no interest in the previous value. Again, you'll get used to it, even if you are not right now.
  • i++increment i, I am interested in the previous value, though
  • ++iincrement i, I am interested in the current valueincrement i, no interest in the previous value。再一次,你会习惯它,即使你现在不习惯。

Knuth.

克努特。

Premature optimization is the root of all evil. As is premature pessimization.

过早的优化是万恶之源。过早的悲观也是如此。

回答by James Sutherland

It's not entirely correct to say that the compiler can't optimize away the temporary variable copy in the postfix case. A quick test with VC shows that it, at least, can do that in certain cases.

说编译器不能在 postfix 情况下优化掉临时变量副本并不完全正确。对 VC 的快速测试表明,它至少可以在某些情况下做到这一点。

In the following example, the code generated is identical for prefix and postfix, for instance:

在以下示例中,前缀和后缀生成的代码相同,例如:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

Whether you do ++testFoo or testFoo++, you'll still get the same resulting code. In fact, without reading the count in from the user, the optimizer got the whole thing down to a constant. So this:

无论您执行 ++testFoo 还是 testFoo++,您仍然会得到相同的结果代码。事实上,没有从用户那里读取计数,优化器就将整个事情归结为一个常数。所以这:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

Resulted in the following:

结果如下:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

So while it's certainly the case that the postfix version could be slower, it may well be that the optimizer will be good enough to get rid of the temporary copy if you're not using it.

因此,虽然 postfix 版本肯定会变慢,但如果您不使用它,优化器很可能足以摆脱临时副本。

回答by martjno

The Google C++ Style Guidesays:

谷歌C ++风格指南说:

Preincrement and Predecrement

Use prefix form (++i) of the increment and decrement operators with iterators and other template objects.

Definition:When a variable is incremented (++i or i++) or decremented (--i or i--) and the value of the expression is not used, one must decide whether to preincrement (decrement) or postincrement (decrement).

Pros:When the return value is ignored, the "pre" form (++i) is never less efficient than the "post" form (i++), and is often more efficient. This is because post-increment (or decrement) requires a copy of i to be made, which is the value of the expression. If i is an iterator or other non-scalar type, copying i could be expensive. Since the two types of increment behave the same when the value is ignored, why not just always pre-increment?

Cons:The tradition developed, in C, of using post-increment when the expression value is not used, especially in for loops. Some find post-increment easier to read, since the "subject" (i) precedes the "verb" (++), just like in English.

Decision:For simple scalar (non-object) values there is no reason to prefer one form and we allow either. For iterators and other template types, use pre-increment.

预增和预减

将递增和递减运算符的前缀形式 (++i) 与迭代器和其他模板对象一起使用。

定义:当变量自增(++i 或 i++)或自减(--i 或 i--)且不使用表达式的值时,必须决定是预增(减量)还是后增(减量)。

优点:当返回值被忽略时,“pre”形式(++i)永远不会比“post”形式(i++)效率低,而且通常更有效。这是因为后递增(或递减)需要制作 i 的副本,这是表达式的值。如果 i 是迭代器或其他非标量类型,则复制 i 可能很昂贵。由于忽略值时两种类型的增量表现相同,为什么不总是预增量?

缺点:在 C 中开发的传统是在不使用表达式值时使用后增量,尤其是在 for 循环中。有些人发现后增量更容易阅读,因为“主语”(i)在“动词”(++)之前,就像英语一样。

决定:对于简单的标量(非对象)值,没有理由偏爱一种形式,我们也允许。对于迭代器和其他模板类型,使用预增量。

回答by martjno

I would like to point out an excellent post by Andrew Koenig on Code Talk very recently.

我想指出 Andrew Koenig 最近在 Code Talk 上发表的一篇精彩文章。

http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29

http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29

At our company also we use convention of ++iter for consistency and performance where applicable. But Andrew raises over-looked detail regarding intent vs performance. There are times when we want to use iter++ instead of ++iter.

在我们公司,我们还使用 ++iter 的约定来保证适用的一致性和性能。但安德鲁提出了关于意图与性能的被忽视的细节。有时我们想使用 iter++ 而不是 ++iter。

So, first decide your intent and if pre or post does not matter then go with pre as it will have some performance benefit by avoiding creation of extra object and throwing it.

因此,首先确定您的意图,如果 pre 或 post 无关紧要,则使用 pre,因为它可以避免创建额外的对象并抛出它,从而带来一些性能优势。

回答by Motti

@Ketan

@Ketan

...raises over-looked detail regarding intent vs performance. There are times when we want to use iter++ instead of ++iter.

...提出了有关意图与性能的被忽视的细节。有时我们想使用 iter++ 而不是 ++iter。

Obviously post and pre-increment have different semantics and I'm sure everyone agrees that when the result is used you should use the appropriate operator. I think the question is what should one do when the result is discarded (as in forloops). The answer to thisquestion (IMHO) is that, since the performance considerations are negligible at best, you should do what is more natural. For myself ++iis more natural but my experience tells me that I'm in a minority and using i++will cause less metal overhead for mostpeople reading your code.

显然 post 和 pre-increment 有不同的语义,我相信每个人都同意在使用结果时应该使用适当的运算符。我认为问题是当结果被丢弃时应该怎么做(如在for循环中)。这个问题的答案(恕我直言)是,由于性能考虑最多可以忽略不计,你应该做更自然的事情。对我自己++i来说更自然,但我的经验告诉我,我是少数,i++对于大多数阅读你的代码的人来说,使用它会导致更少的金属开销。

After all that's the reason the language is not called "++C".[*]

毕竟这就是语言不被称为“ ++C”的原因。[*]

[*] Insert obligatory discussion about ++Cbeing a more logical name.

[*] 插入关于++C成为一个更合乎逻辑的名字的强制性讨论。

回答by Hans Malherbe

  1. ++i- faster not usingthe return value
  2. i++- faster usingthe return value
  1. ++i-不使用返回值更快
  2. i++-使用返回值更快

When not usingthe return value the compiler is guaranteed not to use a temporary in the case of ++i. Not guaranteed to be faster, but guaranteed not to be slower.

不使用返回值时,编译器保证不会在++i的情况下使用临时值。不保证更快,但保证不会更慢。

When usingthe return value i++allows the processor to push both the increment and the left side into the pipeline since they don't depend on each other. ++i may stall the pipeline because the processor cannot start the left side until the pre-increment operation has meandered all the way through. Again, a pipeline stall is not guaranteed, since the processor may find other useful things to stick in.

使用返回值i++允许处理器将增量和左侧都推送到管道中,因为它们不相互依赖。++i 可能会停止流水线,因为处理器无法启动左侧,直到预增量操作一直蜿蜒通过。同样,不能保证流水线停顿,因为处理器可能会发现其他有用的东西要坚持。

回答by bwDraco

The performance difference between ++iand i++will be more apparent when you think of operators as value-returning functions and how they are implemented. To make it easier to understand what's happening, the following code examples will use intas if it were a struct.

之间的性能差异++ii++当你认为运营商作为返回值的功能,以及它们是如何实现将更加明显。为了更容易理解正在发生的事情,以下代码示例将使用int它,就好像它是一个struct.

++iincrements the variable, thenreturns the result. This can be done in-place and with minimal CPU time, requiring only one line of code in many cases:

++i增加变量,然后返回结果。这可以就地完成,并且占用最少的 CPU 时间,在许多情况下只需要一行代码:

int& int::operator++() { 
     return *this += 1;
}

But the same cannot be said of i++.

但不能这样说i++

Post-incrementing, i++, is often seen as returning the original value beforeincrementing. However, a function can only return a result when it is finished. As a result, it becomes necessary to create a copy of the variable containing the original value, increment the variable, then return the copy holding the original value:

后递增,i++通常被视为递增之前返回原始值。但是,函数只能在完成时返回结果。因此,有必要创建包含原始值的变量的副本,增加变量,然后返回包含原始值的副本:

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

When there is no functional difference between pre-increment and post-increment, the compiler can perform optimization such that there is no performance difference between the two. However, if a composite data type such as a structor classis involved, the copy constructor will be called on post-increment, and it will not be possible to perform this optimization if a deep copy is needed. As such, pre-increment generally is faster and requires less memory than post-increment.

当前增量和后增量之间没有功能差异时,编译器可以进行优化,使得两者之间没有性能差异。但是,如果涉及复合数据类型,例如 astructclass,则将在后增时调用复制构造函数,如果需要深复制,则无法执行此优化。因此,预增量通常比后增量更快并且需要更少的内存。

回答by 0124816

Mark: Just wanted to point out that operator++'s are good candidates to be inlined, and if the compiler elects to do so, the redundant copy will be eliminated in most cases. (e.g. POD types, which iterators usually are.)

马克:只是想指出 operator++ 是内联的好候选者,如果编译器选择这样做,在大多数情况下冗余副本将被消除。(例如,迭代器通常是 POD 类型。)

That said, it's still better style to use ++iter in most cases. :-)

也就是说,在大多数情况下使用 ++iter 仍然是更好的风格。:-)