【问题标题】:Speed accessing a std::vector by iterator vs by operator[]/index?通过迭代器与通过运算符 []/索引访问 std::vector 的速度?
【发布时间】:2011-02-01 05:26:36
【问题描述】:

说,我有一个

std::vector<SomeClass *> v;

在我的代码中,我需要在程序中经常访问它的元素,向前和向后循环它们。

这两种访问类型中哪一种是最快的?

迭代器访问:

std::vector<SomeClass *> v;
std::vector<SomeClass *>::iterator i;
std::vector<SomeClass *>::reverse_iterator j;

// i loops forward, j loops backward
for( i = v.begin(), j = v.rbegin(); i != v.end() && j != v.rend(); i++, j++ ){
    // some operations on v items
}

下标访问(按索引)

std::vector<SomeClass *> v;
unsigned int i, j, size = v.size();

// i loops forward, j loops backward
for( i = 0, j = size - 1; i < size && j >= 0; i++, j-- ){
    // some operations on v items
}

而且,如果我不必修改向量元素,const_iterator 是否提供了一种更快的访问向量元素的方法?

【问题讨论】:

  • 分析器结果向您展示了什么?
  • 如果我有时间并且愿意分析我不会在这里问的代码。我只是想知道 stl 迭代器实现是否有某种访问优化。
  • 如果向量拥有对象,请考虑使用boost::ptr_vector。否则使用boost::reference_wrapper
  • @Space_C0wb0y 'boost::ptr_vector'(在我的情况下)是否比 std::vector 快?

标签: c++ stl vector iterator performance


【解决方案1】:

性能差异可能可以忽略不计或没有(编译器可能会将它们优化为相同);您应该担心其他事情,例如您的程序是否正确(缓慢但正确的程序比快速且不正确的程序要好)。不过,使用迭代器还有其他优点,例如能够在不修改循环的情况下将底层容器更改为没有 operator[] 的容器。请参阅this question 了解更多信息。

与普通迭代器相比,const_iterators 很可能没有或可以忽略的性能差异。它们旨在通过防止修改不应修改的内容来提高程序的正确性,而不是为了性能。一般情况下,const 关键字也是如此。

简而言之,在发生以下两件事之前,您不应该担心优化:1) 您注意到它运行太慢和 2) 您已经分析了瓶颈。对于 1),如果它的运行速度比它可以运行的慢十倍,但只运行一次并且需要 0.1 毫秒,谁在乎呢?对于 2),确保它绝对是瓶颈,否则优化它对性能几乎没有可衡量的影响

【讨论】:

  • 我不同意。如果使用索引而不是迭代器可以提高性能,只需使用整数索引。使用索引不会丢失任何东西,而且在我看来它实际上看起来更干净(for( vector&lt;Object&gt;::iterator iter = list.begin() ; vs for( int i = 0 ;
  • @bobobobo - 但是你也失去了轻松交换容器的能力,就像我提到的那样 - 你不能使用带有整数索引的 std::list。
  • 在不了解情况的情况下用“你不应该关心”来回答这样的问题似乎是不合适的。
  • 同意亨特的观点。没有回答问题,而是傲慢地说道“你应该这样做”。
  • 我也同意 Hunters 的评论。这种提供帮助的方式在很大程度上是不恰当的,老实说,这看起来就像是在试图解决问题时气馁。
【解决方案2】:

一个简单的基于循环的基准测试已经完成。我使用的是 VS 2010 SP1(发布配置)。

  1. 使用迭代器 (*it = *it + 1;)
  2. 使用索引 (vs[i] = vs[i] + 1;)

在数十亿次迭代中,第二种方法被证明要快一点,快了 1%。结果 (索引比迭代器稍快) 是可重现的,但正如我所说,差异非常小。

【讨论】:

    【解决方案3】:

    我昨天做了一个测试,使用 [] vs iterator,代码是创建一个包含一些元素的向量并从向量中删除一些元素。 这是使用运算符 [] 访问元素的代码

      TimeSpent([](){
        std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
        for (int i = int(vt.size()) - 1; i >= 0; i--)
        {
          if (vt[i] % 2 == 0)
          {
            //cout << "removing " << vt[i] << endl;
            vt.erase(vt.begin() + i);
          }
        }
      });
    

    以下代码是关于使用迭代器访问向量元素

      TimeSpent([](){
        std::vector<int> vt = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
        for (std::vector<int>::iterator num = vt.begin(); num != vt.end();)
        {
          if (*num % 2 == 0)
          {
            num = vt.erase(num);
          }
          else
          {
            ++num;
          }
        }
      });
    

    通过这个函数分别调用它们进行测试

    void TimeSpent(std::function<void()> func)
    {
      const int ONE_MIL = 10000;
      long times = ONE_MIL;
      std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
      while (times > 0)
      {
        func();
        --times;
      }
      std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
      cout << "time elapsed : " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << endl;
    }
    


    测试环境为visual studio 2013 pro。版本 4.5.51650
    结果是:
    运算符 [] : 192
    迭代器:212
    总结:当我们访问vector容器时,operator []比iterator快。

    【讨论】:

      【解决方案4】:

      我相信向量迭代器在内部被实现为指针(在一个好的 STL 实现中),所以一般来说这两个习语之间的性能差异应该可以忽略不计。但是如果你想知道这些在你的平台上的表现如何,你为什么不用一个小测试程序来测量它呢?我认为测量执行时间不会超过 5 分钟,例如两种变体都进行了 100 万次迭代...

      【讨论】:

        【解决方案5】:

        一如既往,视情况而定。通常我认为您不会看到任何差异,但只有您可以通过分析代码来确定这一点。一些编译器将向量迭代器实现为原始指针,而有些则没有。此外,在调试版本中,某些编译器可能正在使用检查迭代器,这可能会更慢。但在生产模式下可能没有什么不同。剖析一下看看。

        【讨论】:

          【解决方案6】:

          通过优化 (-O2),时间应该会得到改善(应该几乎相同)。

          【讨论】:

            【解决方案7】:

            就速度而言,我认为可能几乎相同。 更好的是,您仍然可以进行概要分析和检查。

            至少你可以减少使用的变量数量:)

            for( i = 0; i < size ; i++){
                // some operations on v items
                v[i];
                v[size-i+1];
            }
            

            关于const_iterator:请参考我的Q:Are const_iterators faster ?

            【讨论】:

            • 您确定每个循环的“size - i + 1”都比“j--”或更好的“--j”快吗?我认为不,所以我更喜欢有更多的变量和更少的时钟:P
            • 我猜这些是微优化,正如人们所说,微优化是邪恶的!
            • @Simone:我认为这是一个过早的优化。编译器很可能会为 aJ 的示例生成最佳代码。同样,如果您不进行个人资料分析,您将不会知道。
            【解决方案8】:

            我会选择迭代器,但我要优化的是在循环中调用end() 并将前置增量更改为后置增量。 IE。我会

            std::vector<SomeClass *> v;
            std::vector<SomeClass *>::iterator i,ie;
            std::vector<SomeClass *>::reverse_iterator j,je;
            
            // i loops forward, j loops backward
            for( i=v.begin(),ie=v.end(), j=v.rbegin(),je=v.rend(); i!=ie && j!=je; ++i,++j ){
                // some operations on v items
            }
            

            而且我不认为这是过早的微优化,它只是编写更好的代码。远比调用所有尝试编写高效代码过早的微优化和用分析代替思考要糟糕得多。

            【讨论】:

            • 为什么不在测试中删除j!=je,因为这两个条件是相同的?
            • rafak,条件并不相同,即使它们应该重合。我只是保留了原始逻辑,这在一定程度上是有效的——在安全方面没有任何问题。不过,我不太可能在我的代码中同时保留这两个条件。
            【解决方案9】:

            我对类似的事情感到困惑并编写了一个程序来测试性能:https://github.com/rajatkhanduja/Benchmarks/blob/master/C%2B%2B/vectorVsArray.cpp

            以下是在具有 7.7 GB RAM 的 Linux-i686(64 位机器)上使用 g++(没有任何优化标志)读取/写入大小为 1m 的向量 的相关观察:-

            使用索引写入向量所需的时间。 : 11.3909 毫秒

            按顺序使用索引从向量中读取所花费的时间。 : 4.09106 毫秒

            使用索引从向量中随机读取所花费的时间。 : 39 毫秒

            使用迭代器(按顺序)写入向量所花费的时间。 : 24.9949 毫秒

            使用迭代器(按顺序)从向量中读取所花费的时间。 : 18.8049 毫秒

            【讨论】:

            • 你是什么编译器标志? for (int i = 0; i &lt; ARR_SIZE; i++) { tmp = v[i]; } 可以很容易地被任何智能编译器优化为单个语句。事实上,大多数编译器都会注意到您甚至不会在任何地方读取变量。该代码仍然需要 4 毫秒的事实表明您可能在完全禁用优化的情况下进行编译,这会使您的基准测试非常具有误导性。
            【解决方案10】:

            您不仅在过早地进行优化,而且还在进行微优化。这是一个几乎和前者一样糟糕的邪恶(不同之处在于,非常、非常、非常罕见地实际上需要进行微优化)。将两者放在一起,您就有了灾难的秘诀。

            如果您运行分析器并发现该代码区域是一个瓶颈,那么您将需要进行优化。您不会通过尝试将循环从 23 个时钟周期减少到 22 个来进行优化。您通过寻找减少算法的 O() 的方法来进行优化。但在您运行分析器之前,您应该更关注设计而不是其他任何关注点。

            【讨论】:

              猜你喜欢
              • 2014-11-04
              • 1970-01-01
              • 2010-12-12
              • 2019-01-05
              • 1970-01-01
              • 2010-09-29
              • 2018-04-22
              • 2018-06-18
              • 1970-01-01
              相关资源
              最近更新 更多