std :: vector索引总是更快吗?

时间:2012-03-16 09:57:24

标签: c++ stl indexing iterator stdvector

当使用std :: vector时,通过索引而不是使用迭代器来传递所有vector的元素总是更快?

我写了简单的愚蠢测试,VS 2010,优化被禁用

#include <vector>
#include <iostream>
#include <ctime>

const int SIZE = 100000;    

int main()
{    
    std::vector<int> vInt;
    int i, temp;
    srand(time(0));
    for(i = 0; i<SIZE; i++)
        vInt.push_back(rand());

    time_t startTime, endTime;
    std::vector<int>::iterator it = vInt.begin(), itEnd = vInt.end();
startTime = clock();
for( ; it != itEnd; it++)
        temp = *it;
    endTime = clock();
    std::cout<<"Result for iterator: "<<endTime - startTime;

    i = 0;
    int size = vInt.size();
    startTime = clock();
    for(; i<size; i++)
        temp = vInt[i];
    endTime = clock();
    std::cout<<"\nResult for index: "<<endTime - startTime;    

    return 0;
}

DEBUG模式:

没有优化结果

Result for iterator: 143
Result for index: 5

表示const int SIZE = 1000;

Result for iterator: 0
Result for index: 0

表示const int SIZE = 10000;

Result for iterator: 15
Result for index: 2

表示const int SIZE = 1000000;

Result for iterator: 1377
Result for index: 53

使用/ O2标志

表示const int SIZE = 10000;

Result for iterator: 0 - 2
Result for index: 0

表示const int SIZE = 100000;

Result for iterator: 12 - 15
Result for index: 0

表示const int SIZE = 1000000;

Result for iterator: 133 - 142
Result for index: 0 - 3

所以最好总是使用带矢量的索引吗?

更新

RELEASE MODE

使用 / O2标志,所有结果均为0.

禁用优化 / Od 索引更快

表示const int SIZE = 100000000;

Result for iterator: 2182
Result for index: 472

表示const int SIZE = 1000000;

Result for iterator: 22
Result for index: 5

表示const int SIZE = 100000;

Result for iterator: 2 - 3
Result for index: 0 - 1

4 个答案:

答案 0 :(得分:5)

首先,你应该使用任何惯用的用例。如果你正在迭代我会使用迭代器,如果你正在执行随机访问,那么索引。

现在回答实际问题。性能很难,即使测量性能也是一个难题,它要求您清楚地了解要测量的内容,测量方式以及不同的测试结果。理想情况下,您希望隔离测试以使测量尽可能精确,然后多次运行测试以验证结果是否一致。除了完全优化的代码之外,您甚至不应该考虑使用真正的程序来测量性能。如果您处于调试模式且程序很慢,那么最佳优化只是在发布模式下进行编译,增加优化级别,减少库中的调试结构。所有这些改进都是免费的。

在你的测试中,有太多的未知数和变量实际上从中产生了任何东西。编译器标志和选项可以产生很大的影响,因此学习如何使编译器生成更快的代码。向量的迭代器的简单实现是普通指针,因此您可以使用它来获得基本测量:

int *it=&v[0], *end=it+v.size();
for (; it!=end; ++it) temp=*it;

这应该为您提供一个比较您的迭代器的基线。迭代器和此基线之间的性能差异是由于编译器/库供应商用于调试的额外检查。阅读有关如何禁用它们的文档。另请注意,您的步骤(it++)需要创建it的一个副本,在指针大小写中基本上没有效果,但如果迭代器保持任何状态,则{{1}的成本将支配整个循环。总是更喜欢it++

接下来是您要测量的内容以及编译器认为您需要的内容。您希望测量迭代,但编译器不知道它,它只看到您的代码,优化器将尽最大努力生成尽可能快的等效代码。仅使用其中一个循环,编译器可以意识到整个迭代除了将temp设置为++it之外没有任何副作用,并且在最坏的情况下(对于您的测量)它实际上可以执行该转换,完全删除循环,这引导我们到下一点:

魔鬼在于细节。我的猜测是你的意图是在一般情况下测量迭代的相对成本,但是你的程序正在测量迭代恒定大小的向量的成本。为什么这很重要?编译器执行循环展开以避免测试成本。如果编译器知道您的循环将始终包含多个X迭代,那么它只能在每个X步骤中的一个步骤中测试循环完成。优化不能得到保证,在应用程序的一般情况下,编译器不会知道迭代次数,因此您应该确保测试不会为编译器提供比实际程序更多的优化机会。在这两种情况下,您希望确保编译器没有在实际情况下不具备的额外信息,也就是说,您希望隐藏测试知识并强制它专注于问题。我建议你将循环移动到不同的转换单元中的函数,通过引用传递向量,并确保它不能避免循环(将整数作为参数并使用临时和每个元素应用二元运算符在向量中,将所有操作的结果返回给调用者;在处理该函数时,编译器将无法做任何太聪明的事情)

但最重要的是,我必须将你引回第一段,做什么是惯用的。编译器针​​对惯用代码进行了优化,并且当需要性能时,它们将做正确的事情。在这个答案中循环的性能,或问题中的两个循环与优化构建中未经检查的迭代器相同,而不是迭代的成本本身通常会产生任何影响。你的申请表现。

答案 1 :(得分:3)

不,这取决于为编译设置的编译器和优化标志。

即使您发现编译器总是为索引生成更快的代码,您也不应该认为迭代器是无用的。迭代器的优点是它们为所有STL容器提供统一的接口,这意味着您可以编写通用模板函数,通过接受一对迭代器,它们不仅可以使用向量,还可以使用链表和其他容器。 / p>

另外,你应该使用预增量运算符而不是后增量,这应该更快:

for( ; it != itEnd; ++it)

答案 2 :(得分:3)

由于缓存效应,比较不公平。

首先,如果你正在使用VS,请在“Release”模式下编译。或者在gcc中-O2

使用迭代器访问数组元素后,大多数都将被缓存。因此,当您使用索引立即访问它们时,缓存已经“预热”了。尝试在两个单独的运行中执行它:一个只使用迭代器,另一个只使用索引。此外,尝试更大的数据集,比如说1GB。由于clock不是一个非常细粒度的计时器,您可能也想使用rdtsc

仅供参考,这里是a thread talking about stl:vector iterator vs. index.

答案 3 :(得分:0)

我认为调试模式下的迭代器时间会显示广泛的迭代器验证的开销。

没有这个验证开销,但也没有优化,我认为迭代器应该稍慢。

您可以将迭代器视为向量的内部数组的直接指针,因此它们只需要一个取消引用来获取数据,而按索引查找则需要首先添加,然后取消引用。 但在优化器完成后,您不太可能注意到差异。 但重要的是,如果您将代码更改为使用其他容器类型,则可能必须使用迭代器。