++iterator和iterator++之间的性能差异是什么?

29

我的同事声称,对于对象类型,前增量比后增量更有效。

例如:

std::vector<std::string> vec;

... insert a whole bunch of strings into vec ...

// iterate over and do stuff with vec.  Is this more efficient than the next 
// loop?
std::vector<std::string>::iterator it;
for (it = vec.begin(); it != vec.end(); ++it){

}

// iterate over and do stuff with vec.  Is this less efficient than the previous loop?
std::vector<std::string>::iterator it;
for (it = vec.begin(); it != vec.end(); it++){

}

1
非常接近于 https://dev59.com/tHVD5IYBdhLWcg3wTJvF 的一个副本。虽然这个问题确实指定了迭代器,但是链接的问题更为一般化。 - Eclipse
谢谢,那个链接有一个很好的答案。 - hookenz
7个回答

55

后自增必须返回迭代器在增加之前的值;因此,在使用自增运算符进行修改之前,需要将先前的值复制到某个位置,以便可以返回它。这项额外工作可能很少或者很多,但肯定不能比预先自增更少,因为预先自增可以直接执行增加操作并返回刚刚修改的值——无需复制/保存等操作。

因此,除非您特别需要后自增(因为您以某种方式使用“增加前的值”),否则应始终使用预先自增。


标准迭代器没有比内置类型慢的理由;编译器不是可以假定 std:: 中的内容遵循标准规范,因此可以像对待内置类型一样进行优化吗? - derobert
2
derobert: 是的,如果可以的话。并非所有迭代器类型都简单到可以进行这种优化。如果对象足够大,可能会导致减速。这个情况几乎不会影响程序性能,但人们在紧密循环中使用这种惯用语法时,它确实起到了作用。 - quark
这是一种流行的民间传说式问题。听到一些真实的细节感觉很不错。 - Brandon Pelfrey

18

默认的递增运算符会像这样:

class X
{
    X operator++(int)  // Post increment
    {
        X  tmp(*this);
        this->operator++(); // Call pre-increment on this.
        return tmp;
    }
    X& operator++()  // Pre increment
    {
        // Do operation for increment.
        return *this;
    }
};

需要注意的是,后缀递增运算符是基于前缀递增运算符定义的。因此,后缀递增运算符执行相同的操作并增加了一些额外操作。通常情况下,这会构造一个副本然后通过复制返回该副本(请注意,前缀递增运算符可以通过引用返回自身,但后缀递增运算符不能这样做)。


5
对于基本类型,我曾经这样做。在1998/1999年的Visual Studio中,它使我的许多程序提高了约10%。不久之后,他们修复了优化器。后置递增运算符在堆栈上创建一个变量,复制值,增加源,然后从堆栈中删除变量。一旦优化器检测到它没有被使用,好处就消失了。
大多数人没有停止这种编码方式。 :) 我花了几年时间才做出改变。
对于对象,我不确定它如何工作。我认为需要一个副本运算符来实现后置递增。它必须制作一个副本并使用副本进行操作,并增加源。
Jacob

4

我了解到对于具有良好优化器的编译器来说,使用前缀递增和后缀递增并没有实质性的区别。因此,将前缀递增作为良好的编程习惯是有意义的。这样,如果程序员遇到“次优”的编译器,也可以保证最佳的结果。


3
严肃地说,除非你因为使用后置递增而导致性能问题,在极少数情况下,性能差异才会有所影响

更新

例如,如果你的软件是气候模拟程序,并且operator++会使模拟向前移动一步(即++simulation给出模拟的下一步,而simulation++则复制当前模拟步骤,推进模拟,然后将复制的步骤交给你),那么性能就会成为一个问题。

更现实的情况是,在评论中,原问题提出者提到我们正在讨论一个嵌入式项目,具有(显然)实时要求。在这种情况下,您需要实际查看您的特定实现。我真的不认为使用后置递增而不是前置递增来迭代一个std::vector会有明显的减速,尽管我没有在这个问题上对任何STL实现进行基准测试。


不要因为性能原因选择其中之一。根据清晰度/维护原因选择其一。个人默认使用前置递增,因为我通常不关心递增之前的值。

如果你看到++i而不是i++时会感到头痛,那么也许是时候考虑改行了。


2
正确。但是你应该更喜欢前增量。原因是当有人在系统中更改底层类型时,您不需要返回并验证所有后增量是否有效。因此,为了可维护性,您应该更喜欢前增量。 - Martin York
我目前还没有性能问题,但在我正在处理的系统中考虑性能是值得的。该软件在电信领域运行,每秒处理数千个呼叫。我想简单地询问是否存在性能差异,显然是存在的。 - hookenz
非常正确。但问题只想知道哪个更有效率。 - n002213f
2
在任何合理的实现中,预增量至少与后增量一样快。如果有差异,应使用预增量。如果没有差异,您可以使用预增量。 - David Thornley
图像处理。是的,它很重要。 - hookenz


0

我知道这就像航空业中的“飞行员谈话”。我相信这纯粹是学术性的,如果它真的有什么区别,你需要重新思考你的代码。

例如:我在回答一些关于性能调优的问题时得到了一个评论,内容如下:

个人:我尝试了你随机停止并查看调用堆栈的方法。我做了几次,但毫无意义。所有时间都深入到某个迭代器增量代码中。那有什么好处呢?

我:太好了!这意味着几乎所有的时间都花在了增加迭代器上。只需查看堆栈更高的位置并查看其来自何处。将其替换为简单的整数增量,您将获得巨大的加速。

当然,您可能更喜欢迭代器增量的美观性,但很容易找出它是否会影响性能。


你的环境中没有可用的分析器吗?它们可以以比随机停止堆栈更稳健的方式获取相同的信息。采样分析器本质上也是这样做的,只不过更频繁和自动化。 - Blaisorblade
@Blaisorblade:请查看PDF幻灯片展示 *这里*。性能分析工具基于低预期。 - Mike Dunlavey
我得试一下,但你的想法有些道理。 - Blaisorblade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接