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

c++的答案是什么?


当前回答

Mark:只是想指出操作符++是很好的内联候选者,如果编译器选择这样做,在大多数情况下多余的拷贝将被消除。(例如POD类型,迭代器通常是这种类型。)

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

其他回答

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

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

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

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

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

i++有时比++ I快!

对于使用ILP(指令级并行)的x86架构,i++在某些情况下可能优于++i。

为什么?因为数据依赖关系。现代cpu可以并行化很多东西。如果接下来的几个CPU周期对i的增量值没有任何直接依赖,CPU可能会省略微码来延迟i的增量,并将其塞到“空闲插槽”中。这意味着您实际上得到了一个“免费”增量。

我不知道ILE在这种情况下走多远,但我认为如果迭代器变得太复杂,并做指针解引用,这可能不会工作。

下面是Andrei Alexandrescu对这个概念的解释:https://www.youtube.com/watch?v=vrfYLlR8X8k&list=WL&index=5

说编译器不能优化掉后缀情况下的临时变量副本是不完全正确的。用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)] 

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

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

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

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

是时候给人们提供智慧的宝石了;)-有一个简单的技巧可以让c++的后缀增量表现得和前缀增量几乎一样(为自己发明的,但我在其他人的代码中也看到了它,所以我不是一个人)。

基本上,诀窍是在返回后使用helper类来延迟增量,然后RAII来拯救

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

Invented用于一些繁重的自定义迭代器代码,它减少了运行时间。前缀vs后缀的成本现在是一个参考,如果这是自定义操作符做大量的移动,前缀和后缀产生了相同的运行时为我。