使用向量时是无符号的整数或迭代器吗?

时间:2018-11-29 21:00:17

标签: c++ iterator

我目前正在和一些朋友一起从事c ++学校项目。

在用c ++编写矢量之前,我做了类似的事情来使用它们:

unsigned int i = 0;
while (i != myVector.size())
{
    doSomething(myVector[i]);
    i++;
}

但是在这个项目期间,我的朋友不高兴看到我使用这样的向量,并要求我使用迭代器。 我不太喜欢迭代器,因为很难记住它们的语法,但是我的朋友们说使用它们会更好,因为它的工作速度更快。 而且由于我们正在处理具有大量向量的大型项目,因此使用迭代器至关重要。

时间过去了,即使我仍然不记得他们的语法,但我仍在使用它们,但我想查看迭代器方法是否真的比“ unsigned int”方法更快。

所以我做了这两个程序:

使用unsigned int方法的第一个程序:

#include <vector>
#include <string>
#include <iostream>

int main()
{
    std::string str = "This is a string";

    int i = 0;
    std::vector<std::string> vec;

    while (i != 10000000)
    {
        vec.push_back(str);
        i++;
    }

    unsigned int j = 0;
    while (j != vec.size())
    {
        std::cout << vec[j] << std::endl;
        j++;
    }
    return (0);
}

第二个程序使用迭代器方法:

#include <vector>
#include <string>
#include <iostream>

int main()
{
    std::string str = "This is a string";

    int i = 0;
    std::vector<std::string> vec;

    while (i != 10000000)
    {
        vec.push_back(str);
        i++;
    }

    std::vector<std::string>::iterator it;
    it = vec.begin();
    while (it != vec.end())
    {
        std::cout << *it << std::endl;
        it++;
    }
    return (0);
}

如您所见,两个程序都将首先创建一个大小为1 000 000的矢量(我放了一个大尺寸,因此如果时间上的差异将更容易发现),然后我将打印出向量中使用字符串,但是使用两种不同的方法。

我在Linux上花费时间来了解每个程序的执行时间,如下所示:

time ./a.out

结果如下:

unsigned int方法:

real    0m39,391s
user    0m5,463s
sys     0m21,108s

迭代器方法:

real    0m39,436s
user    0m5,972s
sys     0m20,652s

然后.........是同一时间?! 两者之间的差异不到1秒,并且是一个包含1000万个字符串的向量。

所以我想知道这两种方法之间真的有区别吗,迭代器真的更好用吗?

2 个答案:

答案 0 :(得分:4)

使用迭代器的主要原因不是性能,而是错误的可能性和表达性更高的可能性。比较

unsigned int i = 0;
while (i != myVector.size())
{
    doSomething(myVector[i]);
    i += 2;
}

unsigned int start = myVector.size() + 42;

for (unsigned int i = start; i != myVector.size(); ++i){
    doSomething(myVector[i]);
}

使用

for (const auto& e : myVector) {
     doSomething(e);
}

基于范围的for循环使迭代器的使用尽可能简单(您甚至看不到迭代器,但它们在后台使用)。当您手动管理索引时,有数百万种方法会弄错索引,而使用迭代器则可能有2或3。

为了进行性能比较:当向量将其元素存储在连续内存中时,向量迭代器可以是普通指针。您认为开销主要是语法糖,使您可以编写更好的代码。因此,您看不出太大的差别并不奇怪。

PS

  

我经常使用它,我有信心不会犯太多错误

根据我自己的经验,我非常了解这种感觉。不久前,我认为我在使用c样式数组的奥秘技术上已经达到了可接受的水平,并且我不愿意使用std::vector,因为我确信它不能像原始数组一样有效。我本来没有错,但这是一个不同的故事。

使用整数来迭代数组是上世纪以来的事情。它不安全,导致难以发现错误,并且可以轻松调用未定义的行为。编写代码来表达您想要做什么,而不是指示处理器。如果要对向量的每个元素进行操作,则应使用std::for_each

std::for_each(myVector.begin(),myVector.end(),doSomething);

它没有手动使用索引的任何缺点(您是否发现了上述循环中的错误?),并且具有无论实际使用的是myVector容器还是哪种容器都具有相同外观的优点。包含的元素或实际上是什么doSomething(可以是自由函数,函子,lambda,您可以选择)。

答案 1 :(得分:3)

令人惊讶的是,在循环中将迭代器访问与索引访问进行比较时,至少在理论上可能存在性能差异,如此处所示:https://gcc.godbolt.org/z/frFYhF

使用迭代器过滤一些噪声,每次迭代看起来像

.LBB0_1:                                # =>This Inner Loop Header: Depth=1
    movl    (%rbx), %edi
    callq   check(int)
    addq    $4, %rbx
    cmpq    %rbx, %r14
    jne     .LBB0_1

因此,在这里我们看到一个内存访问,一个数学运算和一个条件分支。总体而言,一旦您超出了缓存行,内存访问将使所有其他文件相形见

当我们研究索引访问时,迭代看起来像:

.LBB1_3:                                # =>This Inner Loop Header: Depth=1
    movq    (%r14), %rax
    movl    (%rax,%rbx,4), %edi
    callq   check(int)
    addq    $1, %rbx
    cmpq    %r15, %rbx
    jb      .LBB1_3

在这里,我们看到了在上一个示例中没有看到的东西-每次迭代都会有一个额外的寄存器移动(这是位移内存访问所必需的)。

现在,寄存器移动可能是CPU可以执行的最便宜的实际操作之一,但它仍然是一个操作,并且它将是一个重新排序块,因为以后的操作取决于它的结果。

我相信,在访问引导程序时,我们在此处看到的性能影响不应是您在考虑的事情。相反,您应该追求均匀性,易于阅读和可维护性。

话虽如此,我建议您选择基于范围的循环。

for (int i: vec) {
     // work with i
}

最后但不是列表,使用变量unsigned int遍历向量的索引可能是一个讨厌的错误。在许多平台上,向量可能大于maxim int允许的值,您将最终遇到无限循环。

相关问题