【问题标题】:Performance difference between ++iterator and iterator++?++iterator 和 iterator++ 之间的性能差异?
【发布时间】:2010-11-21 04:38:13
【问题描述】:

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

例如

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++){

}

【问题讨论】:

  • 非常接近 *.com/questions/24901/… 的副本。这个问题确实指定了迭代器,而链接的问题更笼统。
  • 感谢该链接有一个很好的答案。

标签: c++ performance


【解决方案1】:

后增量必须返回迭代器在递增之前的值;因此,在使用适当的增量更改它之前,需要将先前的值复制到某个地方,因此它可以返回。额外的工作可能很少或很多,但它肯定不能小于零,与 preincrement 相比,它可以简单地执行递增然后返回刚刚更改的值 -- 不复制 // 保存 //等必要的。

因此,除非您明确必须具有后增量(因为您以某种方式使用“增量前的值”),否则您应该始终使用前增量。

【讨论】:

  • 标准迭代器确实没有任何理由变慢;编译器是否允许假定 std:: 中的内容按照标准指定的方式进行,从而进行与内置类型相同的优化?
  • derobert:是的,如果可以的话。并非所有迭代器类型都足够简单来进行这种优化。如果对象“足够大”,则可以减速。可以说它几乎没有关系,但人们在紧密循环中使用这个成语,它确实可以。
  • 一种流行的民间传说式问题。很高兴听到一些关于它的真实细节。
【解决方案2】:

默认的增量运算符如下所示:

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

请注意,后增量是根据前增量定义的。所以后增量做同样的工作加上一些额外的工作。通常这是构造一个副本,然后通过副本返回副本(注意,前置增量可以通过引用返回自身,而后置增量不能)。

【讨论】:

  • 你有两个后缀。
  • 两个后缀是什么意思?我可以在这里看到 post 和 pre。我什至测试了它。
  • @flyjohny:评论已经有一年了,在我更正之前引用了帖子的第一版:*.com/posts/1304020/revisions
  • @LokIAstari:啊,我明白了 :)
【解决方案3】:

对于原始类型,我曾经这样做过。它曾经让我在 1998/1999 年的 Visual Studio 上的许多程序中获得大约 10% 的提升。不久之后,他们修复了优化器。后自增运算符在堆栈上创建一个变量,复制该值,递增源,然后从堆栈中删除该变量。一旦优化器检测到它没有被使用,好处就消失了。

大多数人并没有停止以这种方式编码。 :) 我花了几年时间。

对于对象,我不确定它是如何工作的。我假设真正实现后增量需要一个复制运算符。它必须制作一个副本并使用该副本进行操作,并增加源。

雅各布

【讨论】:

    【解决方案4】:

    我已经读过,它与具有良好优化器的编译器没有区别。由于两者的整体循环逻辑相同,因此使用预增量作为良好的编程习惯是有意义的。这样,如果程序员遇到“次优”编译器,就可以保证最好的结果。

    【讨论】:

      【解决方案5】:

      说真的,除非由于调用 post-increment 而不是 pre-increment 而实际上遇到了性能问题性能差异只会在非常罕见的情况下产生影响


      更新

      例如,如果您的软件是一个气候模拟,operator++ 使模拟向前推进一步(即++simulation 为您提供模拟的下一步,而simulation++ 复制模拟中的当前步骤,推进模拟,然后将副本交给您)然后性能将成为问题。

      更现实的是,在评论中,最初的提问者提到我们正在讨论一个具有(显然)实时要求的嵌入式项目。在这种情况下,您需要实际查看您的具体实现。我严重怀疑您在使用后增量而不是前增量迭代 std::vector 时是否会出现明显的减速,尽管我还没有就此事对任何 STL 实现进行基准测试。


      出于性能原因,请勿选择其中一个。出于清晰/维护的原因,选择一个。就我个人而言,我默认为预递增,因为我通常不会在乎递增之前的值是多少。

      如果看到 ++i 而不是 i++ 时,你的大脑会爆炸,那么可能是时候考虑换一种工作了。

      【讨论】:

      • 正确。但是您应该更喜欢预增量。原因是当有人更改系统中的基础类型时,您无需返回并验证所有后增量是否有效。因此,为了可维护性,您应该更喜欢预增量。
      • 我还没有性能问题,但考虑我正在开发的系统的性能是值得的。该软件在电信部门运行,每秒处理数千个呼叫。我想我只是问一下是否存在性能差异,显然存在。
      • 非常正确。但问题只是想知道哪个更有效。
      • 在任何合理的实现中,前置增量至少和后置增量一样快。如果有差异,您应该使用预增量。如果没有,你也可以使用预增量。
      • 图像处理。是的,这很重要。
      【解决方案6】:
      【解决方案7】:

      我知道这就像航空中的“机库飞行”。我敢肯定这纯粹是学术性的,你知道它是否会产生重大影响,你需要重新考虑你的代码。

      示例:我对一些关于性能调整的问题的回答得到了评论,它是这样的:

      Person:我尝试了随机停止并查看调用堆栈的方法。我做了几次,但没有任何意义。一直以来,它都深藏在一些迭代器增量代码中。这有什么好处?

      我:太好了!这意味着几乎所有的时间都花在了递增迭代器上。只需向上看堆栈,看看它来自哪里。将其替换为简单的整数增量,您将获得 大量 加速。

      当然,您可能更喜欢迭代器递增的美感,但很容易发现它是否会降低您的性能。

      【讨论】:

      • 您的环境中没有可用的分析器,它允许以比随机停止堆栈更强大的方式获取相同的信息?采样分析器的作用基本相同,只是更加频繁和自动。
      • @Blaisorblade:查看 PDF 幻灯片放映 here。分析器基于低期望。
      • 我必须尝试一下,但你的想法有些道理。