【问题标题】:Efficiency of vector index access vs iterator access向量索引访问与迭代器访问的效率
【发布时间】:2012-03-19 08:18:20
【问题描述】:

我有疑问纠正我对通过使用索引访问(使用运算符 [])或使用迭代器访问向量元素的效率的理解。

我的理解是“迭代器”比“索引访问”更有效。 (我也认为vector::end()vector::size() 更有效)。

现在我编写了示例代码来测量它(在 Windows 7 下使用 Cygwin,使用 g++ 4.5.3)

索引访问循环版本(以前标记为随机访问):

int main()
{
  std::vector< size_t > vec ( 10000000 );
  size_t value = 0;

  for( size_t x=0; x<10; ++x )
  {
    for ( size_t idx = 0; idx < vec.size(); ++idx )
    {
      value += vec[idx];
    }
    return value;
  }
}

迭代器循环代码是这样的:

    for (std::vector< size_t >::iterator iter = vec.begin(); iter != vec.end(); ++iter) {
        value = *iter;
    }

我很惊讶地发现“索引访问”版本要快得多。我使用time 命令来“测量”。数字是:

使用g++ source.cpp 的结果(无优化) 索引访问

真正的 800 毫秒

迭代器访问

真正的 2200 毫秒

这些数字有意义吗? (我多次重复运行)我想知道我错过了哪些细节以及为什么我错了......

使用 g++ -O2 的结果 索引访问,实际时间:~200ms

迭代器访问,实际时间:~200ms

我在不同平台(amd64 w/g++ 和 power7 w xlC)上重复测试,发现我一直使用优化代码,示例程序的执行时间相似。

edit 更改了代码以添加值 (value += *iter),而不仅仅是使用赋值。添加了有关编译器选项的详细信息。添加了使用 -O2 的新数字。 *edit2 将标题更正“迭代器效率”更改为“访问效率”。

【问题讨论】:

  • 确保您没有使用调试支持进行编译,尤其是在 MSVC 下。此外,您的第一个版本根本不使用迭代器,而在第二个版本中,您确实拥有随机访问迭代器。
  • 你开启优化了吗?
  • 你的预感是正确的,经过优化是相反的,#2 更快。
  • 我觉得你很困惑。 Vector 只有随机访问迭代器。使用operator[] 对向量进行索引不涉及迭代器。
  • 替换值 = vec[idx];值 += vec[idx];在这两种情况下,为了避免编译器太聪明以至于发现它被覆盖了

标签: c++ stl vector iterator


【解决方案1】:

没有看到测试工具、编译器选项以及您如何 测量时间,很难说什么。此外,一个好的编译器可能 能够在一种情况下消除循环,因为循环具有 对返回的值没有影响。仍然,取决于 实现,我不会惊讶地看到迭代器 比索引更快(反之亦然)。

关于你的“理解”,没有什么与生俱来的 迭代器的类型及其性能。您可以编写前向迭代器 非常快或非常慢,就像您可以编写随机访问一样 非常快或非常慢的迭代器。在全球范围内,数据类型 支持随机访问迭代器的结构可能具有 比那些没有的地方更好,这可能有利于 随机访问迭代器;但这还不够 任何合理的概括。

【讨论】:

  • 我使用“time”命令测量了执行时间。 “您可以编写非常快或非常慢的前向迭代器”这句话让我挑战了我的简化观点。我必须重新审视我的想法。谢谢!
【解决方案2】:

当我使用-O2(Linux,GCC 4.6.1)编译这两个程序时,它们的运行速度同样快。

那么:你的第一个程序使用迭代器,它使用索引。这些是不同的概念。

您的第二个程序实际上是使用随机访问迭代器,因为std::vector&lt;T&gt;::iterators 就是这样。对std::vector 的限制是这样设计的,即可以将迭代器实现为指向vector 封装的动态数组的简单指针。

begin 应该和size 一样快。在std::vector 的典型实现中两者之间的唯一区别是end 可能需要计算begin() + size(),尽管size 也可能(大致)实现为end() - begin()。不过,编译器可能会在循环中优化两者。

【讨论】:

  • 实际上,我见过的std::vector 的实现保留了三个指针:开始、结束和一个指向分配块末尾的指针。这使得vector&lt;&gt;::size() 稍微慢了一点,因为它必须做end - begin
  • 我认为 gcc 足够聪明,可以推断出 endsize 的值不会改变,因此它不会在每次迭代中计算它..(但请随时检查生成的代码)
  • @yi_H:好点。我实际上误读了这个问题,尽管 OP 正在比较 begin()end() 的性能。
  • 我可以确认:使用 -O2 它们运行速度同样快。我也试过 -O3 和同样的趋势。
【解决方案3】:

通过优化,这两个代码应该(几乎)相同。试试-O2

如果没有优化和添加调试信息,您的测量结果将非常具有误导性。

【讨论】:

    【解决方案4】:

    在您的第一个示例中,您使用 value = vec[idx]; 取消引用每个单独的项目,这会导致每次访问元素时计算 element_size * index 的偏移量。

    由于向量由排列在连续内存块中的元素组成,向量迭代器通常只实现为一个简单的指针,因此遍历向量(如您的第二个示例)只需将指针推进一个元素每次迭代。

    但是,如果您启用优化(尝试 -O2-O3),编译器可能会将第一个示例中的循环优化为类似于第二个示例的内容,从而使性能几乎相同。

    【讨论】:

      【解决方案5】:

      实际上,我发现迭代器更快。尝试将您的迭代器循环重构为如下所示,您也可能会看到:

      #include <ctime>
      #include <vector>
      #include <iostream>
      using namespace std;
      
      int main()
      {   
        std::vector< size_t > vec ( 1000000 );
        size_t value = 0;
        srand ( time(NULL) );
        clock_t start,stop;
        int ncycle = 10000;
      
        start = std::clock();
        for( size_t x=0; x<ncycle; ++x ) { 
          for ( size_t idx = 0; idx < vec.size(); ++idx )
            vec[idx] += rand();
        }   
        stop = std::clock();
        cout << start << " " << stop << endl;
        cout << "INDEX: " << (double((stop - start)) / CLOCKS_PER_SEC) / ncycle << " seconds per cycle" << endl;
      
        start = std::clock();
        for( size_t x=0; x<ncycle; ++x ) { 
          for (std::vector< size_t >::iterator iter = vec.begin(), end = vec.end(); iter != end; ++iter)
              *iter += rand();
        }   
        stop = std::clock();
        cout << "ITERATOR: " << (double((stop - start)) / CLOCKS_PER_SEC) / ncycle << " seconds per cycle" << endl;
      }   
      

      在我的电脑上结果如下,说明迭代器略有领先:

      INDEX: 0.012069 seconds per cycle
      ITERATOR: 0.011482 seconds per cycle
      

      你应该注意到我使用了 rand();这可以防止编译器优化它可以在编译时计算的东西。编译器似乎使用内在数组比使用向量更容易做到这一点,这可能会误导性地赋予数组优于向量的优势。

      我用“icpc -fast”编译了上面的内容。 slavik 关于在使用迭代器(ala 指针)时必须对索引进行计算而不是递增是正确的。

      【讨论】:

      • 你知道“rand()”会超过迭代时间吗?它非常慢。另外:您在循环中不断调用“vec.size()”。这也可能会减慢速度。
      • @Dudeson 查看我的回答,了解使用 rand() 的理由;通话本身在这两种情况下都存在,因此权重相同。我确定编译器优化了 vec.size() 了。
      • 我知道你在这两种情况下都有 rand() 。但我不是 100% 确定 rand() 总是需要相同的时间来执行。反正。我做过一次物理模拟。为此,我有两个像你一样的嵌套 for 循环。去掉vector.size()后,性能明显提升!
      猜你喜欢
      • 2016-08-25
      • 1970-01-01
      • 2010-10-18
      • 2011-07-18
      • 1970-01-01
      • 2015-06-16
      • 2010-10-29
      • 1970-01-01
      • 2016-08-03
      相关资源
      最近更新 更多