std::vector is索引总是更快

std::vector is indexes always faster?

本文关键字:索引 vector is std      更新时间:2023-10-16

使用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;
}

调试模式:

如果没有优化结果,

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

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

更新

释放模式

带有/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

第一件事是,您应该为您的用例使用任何惯用的东西。如果你在迭代,我会使用迭代器,如果你在执行随机访问,那么就使用索引。

现在来谈谈实际问题。性能很难,甚至衡量性能也是一个难题,这需要你清楚地知道你想要衡量什么,你将如何衡量,以及不同的事情将如何影响你的测试。理想情况下,您希望隔离测试,以便测量尽可能精确,然后多次运行测试以验证结果是否一致。你甚至不应该考虑用完全优化的代码来衡量性能,最好是用真正的程序。如果您处于调试模式,并且程序运行缓慢,那么最好的优化就是在发布模式下编译,增加优化级别,减少库中的调试构造。所有这些改进都是免费的。

在你的测试中,有太多的未知和变量,无法真正从中产生任何东西。编译器标志和选项可能会产生很大的影响,所以要学会如何让编译器产生更快的代码。向量迭代器的一个简单实现是一个普通指针,因此您可以使用它来获得基本度量:

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

这将为您提供一条基线,用于比较迭代器。迭代器和该基线之间的任何性能差异都是由于编译器/库供应商用于调试的额外检查造成的。阅读有关如何禁用它们的文档。还要注意,您的步骤(it++)需要创建一个it的副本,在指针的情况下,这基本上没有效果,但如果迭代器保持任何状态,it++的成本将主导整个循环。总是喜欢++it

下一件事是你想要测量什么以及编译器认为你需要什么。您想测量迭代,但编译器不知道,它只看到您的代码,优化器将尽最大努力以尽可能快的速度生成等效的代码。只需要其中一个循环,编译器就可以意识到整个迭代除了将temp设置为v[v.size()-1]之外没有任何副作用,在最坏的情况下(对于您的测量),它实际上可以执行转换,完全删除循环,这将引导我们进入下一点:

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

但最重要的是,我必须让你回到第一段,做惯用的。编译器针对惯用代码进行了优化,当/如果需要性能时,它们会做正确的事情。这个答案中的循环或问题中的两个循环的性能与优化构建中未检查的迭代器的性能相同,而不是迭代本身的成本通常会对应用程序的性能产生任何影响。

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

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

此外,您应该使用前增量运算符而不是后增量运算符,这应该更快:

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

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

首先,如果您在gcc中使用VS.或-O2,请在"Release"模式下编译。

在使用迭代器访问数组元素后,它们中的大部分将被缓存。因此,当您立即使用索引访问它们时,缓存已经"预热"。试着在两个单独的运行中完成:一个只使用迭代器,另一个只只使用索引。另外,尝试更大的数据集,比如1GB。由于clock不是一个非常细粒度的计时器,您可能还需要使用rdtsc

仅供参考,这里有一个关于stl的线程:向量迭代器与索引。

我认为调试模式下的迭代器时间向您展示了大量迭代器验证的开销。

如果没有这种验证开销,也没有优化,我认为迭代器应该稍微慢一点。

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