6 个技巧,提升 C++11 的 vector 性能

点击打开链接

Vector 就像是 C++ STL 容器的瑞士军刀。Bjarne Stoutsoup 有一句话 – “一般情况下,如果你需要容器,就用 vector”。像我们这样的普通人把这句话当作真理,只需要照样去做。然而,就像其它工具一样,vector 也只是个工具,它能提高效率,也能降低效率。

这篇文章中我们可以看到 6 种优化使用 vector 的方法。我们会在最常见的使用 vector 的开发任务中看到有效的方法和无效的方法,并以此衡量有效使用 vector 会带来怎样的性能提升,并试图理解为什么能得到这样的性能提升。


#1 提前分配足够的空间以避免不必要的重新分配和复制周期

程序员喜欢使用 vector,因为他们只需要往向容器中添加元素,而不用事先操心容器大小的问题。但是,如果由一个容量为 0 的 vector 开始,往里面添加元素会花费大量的运行性能。如果你之前就知道 vector 需要保存多少元素,就应该提前为其分配足够的空间。

这里有一个简单的示例,往 vector 里添加 1 万个测试结构的实例——先进行不预分配空间的测试再进行有预分配的测试。

vector<BigTestStruct> testVector1;
vector<BigTestStruct> testVector2;
sw.Restart();
FillVector(testVector1);
cout << "Time to Fill Vector Without Reservation:" << sw.ElapsedUs() << endl;
sw.Restart();
testVector2.reserve(10000);
FillVector(testVector2);
cout << "Time to Fill Vector With Reservation:" << sw.ElapsedUs() << endl;

在我的计算机中,未预分配空间的情况用了 5145 微秒(us),而预分配了空间的情况下只用了 1279 微秒,性能提高了 75.14%!!!

#2 使用 shrink_to_fit() 释放 vector 占用的内存, – clear() 或 erase() 不会释放内存。

与大家所想的相反,使用 erase() 或 clear() 从 vector 中删除元素并不会释放分配给 vector 的内存。做个简单的实验就可以证明这一点。我们往一个 vector 中添加 100 个元素,然后在这个 vector 上调用 clear() 和 erase()。然后我们可以让 capacity() 函数告诉我们为这个容器分配的内存可以存入多少元素。

FillVector(testVector1);
size_t capacity = testVector1.capacity();
cout << "Capacity Before Erasing Elements:" << capacity << endl;
  
testVector1.erase(testVector1.begin(), testVector1.begin() + 3); //
capacity = testVector1.capacity();
cout << "Capacity After Erasing 3 elements Elements:" << capacity << endl;
testVector1.clear();
capacity = testVector1.capacity();
cout << "Capacity After clearing all emements:" << capacity << endl;
testVector1.shrink_to_fit();
capacity = testVector1.capacity();
cout << "Capacity After shrinking the Vector:" << capacity << endl;

下面是输出:

Capacity Before Erasing Elements:12138
Capacity After Erasing 3 elements Elements:12138
Capacity After clearing all emements:12138
Capacity After shrinking the Vector:0

从上面的输出可以看到,erase() 或 clear() 不会减少 vector 占用的内存。如果在代码中到达某一点,不再需要 vector 时候,请使用 std::vector::shrink_to_fit() 方法释放掉它占用的内存。

#3 在填充或者拷贝到 vector 的时候,应该使用赋值而不是 insert() 或push_back().

从一个 vector 取出元素来填充另一个 vector 的时候,常有三种方法 – 把旧的 vector 赋值给新的 vector,使用基于迭代器的 std::vector::insert() 或者使用基于循环的 std::vector::push_back()。这些方法都展示在下面:

vector<BigTestStruct> sourceVector, destinationVector;
FillVector(sourceVector);
// Assign sourceVector to destination vector
sw.Restart();
destinationVector = sourceVector;
cout << "Assigning Vector :" << sw.ElapsedUs() << endl;
//Using std::vector::insert()
vector<BigTestStruct> sourceVector1, destinationVector1;
FillVector(sourceVector1);
sw.Restart();
destinationVector1.insert(destinationVector1.end(),
  sourceVector1.begin(),
  sourceVector1.end());
cout << "Using insert() :" << sw.ElapsedUs() << endl;

这是它们的性能:

赋值: 589.54 us

insert(): 1321.27 us

push_back(): 5354.70 us

我们看到 vector 赋值比 insert() 快了 55.38%,比 push_back() 快了 89% 。

为什么会这样???

赋值非常有效率,因为它知道要拷贝的 vector 有多大,然后只需要通过内存管理一次性拷贝 vector 内部的缓存。

所以,想高效填充 vector,首先应尝试使用 assignment,然后再考虑基于迭代器的 insert(),最后考虑 push_back。当然,如果你需要从其它类型的容器拷贝元素到 vector 中,赋值的方式不可行。这种情况下,只好考虑基于迭代器的 insert()。


#4 遍历 std::vector 元素的时候,避免使用 std::vector::at() 函数。

遍历 vector 有如下三种方法:

  1. 使用迭代器     最快

  2. 使用 std::vector::at() 成员函数  最慢

  3. 使用下标 – [ ] 运算  其次

    #5 尽量避免在 vector 前部插入元素

    任何在 vetor 前部部做的插入操作其复杂度都是 O(n) 的。在前部插入数据十分低效,因为 vector 中的每个元素项都必须为新的项腾出空间而被复制。如果在 vector 前部连续插入多次,那可能需要重新评估你的总体架构。

    做个有趣的尝试,下面是在 std::vector 前部做插入和在 std::list 前部部做插入的对比:

    vector<BigTestStruct> sourceVector3, pushFrontTestVector;
    FillVector(sourceVector3);
    list<BigTestStruct> pushFrontTestList;
    //Push 100k elements in front of the new vector -- this is horrible code !!! 
    sw.Restart();
    for (unsigned i = 1; i < sourceVector3.size(); ++i)
    {
      pushFrontTestVector.insert(pushFrontTestVector.begin(), sourceVector3[i]);
    }
    cout << "Pushing in front of Vector :" << sw.ElapsedUs() << endl;
    // push in front of a list
    sw.Restart();
    for (unsigned i = 0; i < sourceVector3.size(); ++i)
    {
      pushFrontTestList.push_front(sourceVector3[i]);
    }
    cout << "Pushing in front of list :" << sw.ElapsedUs() << endl;

    如果我运行这个测试10,其中使用一个包含100个元素的vector,那么输出结果如下:

    Average of Pushing in front of Vector :11999.4
    Average of Pushing in front of list :20.36

    在 list 前部部插入操作比在 vector 前部部快大约58836%。不用感到奇怪,因为在 list 前部做元素插入的算法,其复杂度为 O(1)。显然,vector 包含元素越多,这个性能测试的结果会越


    #6 在向 vector 插入元素的时候使用 emplace_back() 而不是 push_back()。

    几乎赶上 C++11 潮流的每个人都明确地认同“安置”这种往 STL 容器里插入元素的方法。理论上来说,“安置”更有效率。然而所有实践都表明,有时候性能差异甚至可以忽略不计。

    思考下面的代码:

    vector<BigTestStruct> sourceVector4, pushBackTestVector, emplaceBackTestVector;
    FillVector(sourceVector4);
    //Test push back performance
    sw.Restart();
    for (unsigned i = 0; i < sourceVector4.size(); ++i)
    {
      pushBackTestVector.push_back(sourceVector4[i]);
    }
    cout << "Using push_back :" << sw.ElapsedUs() << endl;
    //Test emplace_back()
    sw.Restart();
    for (unsigned i = 0; i < sourceVector4.size(); ++i)
    {
      emplaceBackTestVector.emplace_back(sourceVector4[i]);
    }
    cout << "Using emplace_back :" << sw.ElapsedUs() << endl;

    如果运行100次,会得到这样的输出:

    Average Using push_back :5431.58
    Average Using emplace_back :5254.64

    可以清楚的看到,“安置”函数比插入函数性能更好 – 但只有 177 微秒的差距。在所有情况下,他们大致是相当的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值