向量索引访问与迭代器访问的效率

Efficiency of vector index access vs iterator access

本文关键字:访问 效率 迭代器 索引 向量      更新时间:2023-10-16

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

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

现在我写了测量它的示例代码(在Windows7下使用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毫秒

迭代器访问

实际2200ms

这些数字有意义吗?(我重复了多次跑步)我想知道我错过了什么细节,为什么我错了。。。

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

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

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

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

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

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

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

然后:您的第一个程序是使用迭代器的而不是,它使用的是索引。这些是不同的概念。

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

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

通过优化,两个代码应该(接近)相同。尝试-O2

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

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

由于向量由在连续内存块中排列的元素组成,因此向量迭代器通常只是作为一个简单的指针来实现,因此遍历向量(就像第二个例子中一样)只需要在每次迭代后将指针前移一个元素。

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

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

#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指针)时必须对索引进行计算,而不是递增。