2012-02-29 107 views
8

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

我的理解是“迭代器”比“索引访问”更有效率。 (我认为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(不优化) 索引访问

真正的800ms

迭代器访问

真正2200ms

,这些数字有意义吗? (我拉肚子多次重复的),我不知道什么细节我想,为什么我错了......

结果使用G ++ -02 索引访问,时间真:〜200ms的

迭代器访问,真实时间:〜200毫秒

我重复不同的平台上测试(AMD64瓦特/ g ++以及POWER7瓦特方xIC)和看到,我使用的所有时间优化的代码的示例方案也有类似的执行时间。

编辑更改代码以添加值(value += *iter)而不是仅使用赋值。增加了关于编译器选项添加了使用-O2的新号码。 * edit2将标题改为“迭代器效率”改为“访问效率”。

+10

确保您没有编译调试支持,尤其是在MSVC下。另外,你的第一个版本根本不使用迭代器,而在第二个版本中你有*随机访问迭代器。 – 2012-02-29 20:24:15

+0

你在使用'-O2' /'-O3'吗? – 2012-02-29 20:25:50

+2

你打开优化了吗? – 2012-02-29 20:26:54

回答

6

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

关于“理解”,关于迭代器类型及其性能没有任何内在的含义。您可以编写快速或非常慢的转发迭代器 ,就像您可以编写非常快或非常慢的迭代器一样。在全球范围内,将支持随机访问迭代器的数据类型可能比没有访问迭代器的数据类型具有更好的局部性,这可能有利于 随机访问迭代器;但这实在不足以使 有任何合理的概括。

+0

我使用“time”命令测量执行时间。陈述“你可以写出转发速度非常快或很慢的迭代器”,这使我挑战了我的简化视图。我必须重新审视我的想法。谢谢! – 2012-02-29 22:35:17

2

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

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

4

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

然后:你的第一个程序是使用迭代器,它使用指数。这些是不同的概念。

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

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

+0

其实,我见过的'std :: vector'的实现保留了三个指针:开始,结束和一个到分配块的末尾。这使得'vector <> :: size()'稍微慢一点,因为它必须做'end - begin'。 – 2012-02-29 20:37:09

+0

我认为gcc足够聪明,可以推断出'end'和'size'值不会改变,因此它不会在每次迭代中计算它。(但可以随时检查生成的代码) – 2012-02-29 20:39:38

+0

@yi_H:好点。我实际上误读了这个问题,尽管OP正在比较'begin()'和'end()'的性能。 – 2012-02-29 20:45:16

0

在第一个示例中,您使用value = vec[idx];取消引用每个单独的项目,这会导致每次访问元素时计算偏移量element_size * index。由于矢量是由连续的内存块中排列的元素组成的,所以矢量迭代器通常只是作为一个简单的指针来实现,所以迭代遍历一个矢量(就像在第二个例子中一样)只需要将指针前进一个元素每次迭代后。然而,如果启用优化(尝试-O2-O3),则编译器可能会优化第一个示例中的循环,类似于第二个示例,从而使性能几乎相同。

0

实际上,我发现迭代器更快。尝试重构你的迭代循环,类似于下面的东西,你可能会看到这个问题,以及:

#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”编译了上面的代码。当使用迭代器(ala指针)时,slavik正确地做了关于必须对指数进行计算与递增计算的说法。

+0

你知道“rand()”会超出迭代时间吗?它非常缓慢。另外:你一直在循环中调用“vec.size()”。这也可能会降低速度。 – Tara 2014-05-05 18:14:41

+0

@Dudeson看到我的答案为使用rand()的理由;通话本身在两种情况下都会因此而平衡。我确信编译器会优化vec.size()。 – Ethereal 2014-05-06 16:44:34

+0

我知道你在这两种情况下都有rand()。但我并不是100%确定rand()总是需要相同的时间来执行。无论如何。我做了一次物理模拟。为此,我有两个像你一样的嵌套for循环。删除vector.size()后,性能明显提高! – Tara 2014-05-06 19:09:56