我们的问题是,在C语言中i++和++i的性能有区别吗?

c++的答案是什么?


当前回答

@Mark:我删除了我之前的答案,因为它有点轻率,仅凭这一点就应该被打反对票。实际上,我认为这是一个很好的问题,因为它问了很多人的想法。

通常的答案是++i比i++快,毫无疑问,但更大的问题是“什么时候应该关心?”

如果增量迭代器所花费的CPU时间小于10%,那么您可能不会在意。

如果增量迭代器所花费的CPU时间百分比大于10%,则可以查看哪些语句正在进行该迭代。看看你是否可以只增加整数而不是使用迭代器。你有可能做到,虽然在某种意义上可能不太理想,但很有可能你会节省花在那些迭代器上的所有时间。

我曾经见过一个例子,其中迭代器增量消耗了超过90%的时间。在这种情况下,采用整数递增法将执行时间减少了这么多。(即优于10倍加速)

其他回答

当您将操作符视为值返回函数以及它们的实现方式时,++i和i++之间的性能差异将更加明显。为了更容易理解发生了什么,下面的代码示例将使用int,就像它是一个结构体一样。

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

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

但是i++就不一样了。

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

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

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

++i -更快,不使用返回值 i++ -使用返回值更快

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

当使用返回值i++时,允许处理器同时推送 增量和左侧进入管道,因为它们彼此不依赖。i可能会使管道停止,因为处理器无法启动左侧,直到增量前操作已经蜿蜒完成。同样,也不保证会出现管道失速,因为处理器可能会找到其他有用的东西来插入。

@wilhelmtell

编译器可以省略临时对象。从另一个线程逐字逐句:

c++编译器允许消除基于堆栈的临时对象,即使这样做会改变程序行为。MSDN链接vc8:

http://msdn.microsoft.com/en-us/library/ms364057 (VS.80) . aspx

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

对于c++来说,答案有点复杂。

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

但是,如果i是c++类的实例,则i++和++i将调用其中一个操作符++函数。下面是这些函数的标准组合:

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;
}

由于编译器不生成代码,而只是调用运算符++函数,因此没有办法优化掉tmp变量及其相关的复制构造函数。如果复制构造函数的开销很大,则会对性能产生重大影响。

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

在下面的例子中,生成的代码对于前缀和后缀是相同的,例如:

#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());
}

无论您使用的是++testFoo还是testfoo++,都将得到相同的结果代码。事实上,无需从用户读取计数,优化器将整个事情归结为一个常数。所以这个:

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

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

结果如下:

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

因此,虽然后缀版本肯定会更慢,但如果你不使用它,优化器可能会足够好,可以摆脱临时副本。