如何清楚容器的缓存_#include


在读一些开源代码时,总是能见到vector在push_back之前进行reserve的操作,以前一直觉得是多此一举,现在看来是自己才疏学浅罢了。

1. 使用reserve的必要性

众所周知,C++的STL容器的内存大小会随着数据数量的增加而增长。对于vector和string来说,每当须要更多的内存空间时(比如push_back操作),该容器就调用与realloc类似的操作。而这个操作大概如下:

  1. 为当前容器分配一块新内存,内存大小为当前容量的某个倍数(通常为2倍);
  2. 将该容器内的全部元素从旧的内存复制到刚才新分配的内存中。
  3. 析构掉旧内存中的对象。
  4. 释放旧内存。

这样以来,随着容器的元素个数的增长,重新分配内存的繁琐操作就会不断进行,大大降低程序的效率。而reserve成员函数可以一次性分配制定大小的内存。在举例子之前,先介绍几个与reserve相关的成员函数(就vector和string来说)。

  • size():容器元素大小,即容器中有多少个元素。
  • capacity():容器的容量,即内存大小,反应了该容器已经分配的内存能够容纳多少个元素。
  • resize(Container::size_type n):强行将容器的元素个数设置为n。如果n比当前的大小要小,则容器尾部的元素将会被析构掉。如果n比当前的大小要大,则通过默认构造函数创建的新元素将被加入到容器的末尾,此时如果该容器的容量不够大,就会重新进行内存分配。
  • reserve(Container::size_type n):通过重新分配内存强行将容器的容量设置为n,n不小于当前大小。

2. reserve示例


#include <iostream>
#include <vector>
#include <chrono>

using namespace std;

int main()
{
    std::chrono::steady_clock::time_point t1, t2;
    double dt;
    int n = 10000000;

    vector<int> vec1, vec2;

    t1 = std::chrono::steady_clock::now();

    for(int i=0; i<n; i++)
        vec1.emplace_back(i);

    t2 = std::chrono::steady_clock::now();
    dt = std::chrono::duration_cast<std::chrono::duration<double> >(t2 - t1).count();

    cout << "vec1.size: " << vec1.size() << endl;
    cout << "vec1.capacity: " << vec1.capacity() << endl;
    cout << "No reserve time: " << dt << endl << endl;

    t1 = std::chrono::steady_clock::now();
    vec2.reserve(n);

    for(int i=0; i<n; i++)
        vec2.emplace_back(i);

    t2 = std::chrono::steady_clock::now();
    dt = std::chrono::duration_cast<std::chrono::duration<double> >(t2 - t1).count();

    cout << "vec2.size: " << vec2.size() << endl;
    cout << "vec2.capacity: " << vec2.capacity() << endl;
    cout << "Use reserve time: " << dt << endl;

    return 0;
}


输出结果如下:


vec1.size: 10000000
vec1.capacity: 16777216
No reserve time: 0.165066

vec2.size: 10000000
vec2.capacity: 10000000
Use reserve time: 0.136831


上面的程序我们对vec1没有使用reserve,直接emplace_back,而对vec2先reserve,在emplace_back。为了让结果对比更明显,这里将n设置为10000000。可以看出使用reserve后,向容器中存入数据的速度明显加快。而且不会出现多余的内存空间,vec1的容量多出了6777216。

3. 如何释放容器中多余的内存

上面的vec1多出的内存是一种浪费,就算使用reserve,我们也可能事先不知道有多少个元素,需要多少内存,只能往大了去reserve,那么针对多出来的内存,如何合理的释放呢。

vector有个成员函数clear(),可以清空所有元素。单数clear只能清空元素,不能释放内存。 在《effective STL》中指明,用clear()无法保证内存回收,但是swap技法可以。具体方法如下所示:


vector<int> ivec;
ivec.push_back(1);
ivec.push_back(1);
ivec.push_back(2);
ivec.push_back(2);
vector<int>().swap(ivec); //或者ivec.swap(vector<int>());


这种方法可以完全清空vector中的元素并释放内存。那么我们不想清空元素,只想释放多出来的内存该怎么做呢。


// 方法一
vector<int>(vec1).swap(vec1);
// 方法二(C++11引入)
vec2.shrink_to_fit();


4. 释放容器中多余内存方法验证


#include <iostream>
#include <vector>

using namespace std;

int main()
{
    int n = 10000000;
    vector<int> vec1, vec2;

    for(int i=0; i<n; i++)
    {
        vec1.emplace_back(i);
        vec2.emplace_back(i);
    }

    cout << "vec1.capacity before swap: " << vec1.capacity() << endl;
    vector<int>(vec1).swap(vec1);
    cout << "vec1.capacity after swap:  " << vec1.capacity() << endl << endl;

    cout << "vec2.capacity before shrink_to_fit: " << vec2.capacity() << endl;
    vec2.shrink_to_fit();
    cout << "vec2.capacity after shrink_to_fit:  " << vec2.capacity() << endl;

    return 0;
}


输出结果如下:


vec1.capacity before swap: 16777216
vec1.capacity after swap:  10000000

vec2.capacity before shrink_to_fit: 16777216
vec2.capacity after shrink_to_fit:  10000000


可见,swap和shrink_to_fit可以达到相同的目的。不过既然C++11了,那还是用shrink_to_fit吧,应该更规范一些。