细节改善效率:std::vector::reserve对性能的提升

  • Post author:
  • Post category:其他




readme

为了支持快速随机访问,vector将元素连续存储。这意味着,在执行push_back时如果内存不足,不能简单的额外分配一块新内存给新元素。

事实上,当push_back遇到内存不足时,vector会重新分配一块更大的连续内存,然后将当前元素移动到新内存中,然后添加新元素,最后释放旧内存。内存的分配拷贝释放,在效率要求苛刻的场景中十分值得优化。

此外,每次分配的新内存大小并不仅仅比之前空间多一个元素,不然每次push_back都要重新分配内存,重新搬运内存,性能会差到受不了。

reserve函数可以在push_back自动重新分配内存之前,预先保留指定大小的内存空间,如果合理使用,可以有效减少内存分配和内存搬运次数,起到性能优化的目的。

这篇博客讨论了reserve对性能的提升。对于性能优化,可以从不同角度着手。相较于reserve对性能带来的优化,优化架构,更好的算法等等措施一般情况下都可以带来更为明显的性能提升。但作为一只对技术有追求的程序猿,都希望写出最好的代码,所以说,这篇博客你若有时间就读读呗~~



测试程序

//common.h
#ifndef _COMMON_H_
#define _COMMON_H_

#include <string>
#include <stdio.h>
#include <set>
#include <memory>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <utility>
#include <algorithm>
#include <map>
#include <numeric>
#include <vector>
#include <iostream>
#include <iterator>
#include <sys/time.h>

using namespace std;
#endif
//Timer.h
#ifndef _TIMER_H
#define _TIMER_H

#include <sys/time.h>
#include "common.h"

class Timer
{
public:
    Timer(string strCallInfo = "Timer")
    {
        _desc = strCallInfo;
        gettimeofday(&_begin,NULL);
        gettimeofday(&_end, NULL);
    };

    ~Timer()
    {
        gettimeofday(&_end,NULL);
        printf("%s, cost %ldms\n", _desc.c_str(), (_end.tv_sec - _begin.tv_sec)*1000 + (_end.tv_usec - _begin.tv_usec)/1000);
    };
private:
    Timer(){};
    string  _desc;

    timeval _begin;
    timeval _end;
};

#endif

//std_reserve.cpp
#include "common.h"
#include "Timer.h"

int main(void){
    int i = 0;
    {
        vector<int> intVector;
        for (i = 0; i < 20; ++i){
            intVector.push_back(i);
            printf("intVector.size(): %ld, intVector.capacity(): %ld\n", intVector.size(), intVector.capacity());
        }
    }
    {
        vector<int> intVector;
        intVector.reserve(20);
        for (i = 0; i < 20; ++i){
            intVector.push_back(i);
            printf("intVector.size(): %ld, intVector.capacity(): %ld\n", intVector.size(), intVector.capacity());
        }
    }
    {
        Timer timer("without reserve");
        vector<int> intVector;
        for (i = 0; i < 10000000; ++i){
            intVector.push_back(i);
        }
    }
    {
        Timer timer("with reserve");
        vector<int> intVector;
        intVector.reserve(10000000);
        for (i = 0; i < 10000000; ++i)
        {
            intVector.push_back(i);
        }
    }
    return 0;

}

编译

g++ -o std_reserve std_reserve.cpp -O2 -g -Wall -std=c++11

执行

[root@***]# ./std_reserve 
intVector.size(): 1, intVector.capacity(): 1
intVector.size(): 2, intVector.capacity(): 2
intVector.size(): 3, intVector.capacity(): 4
intVector.size(): 4, intVector.capacity(): 4
intVector.size(): 5, intVector.capacity(): 8
intVector.size(): 6, intVector.capacity(): 8
intVector.size(): 7, intVector.capacity(): 8
intVector.size(): 8, intVector.capacity(): 8
intVector.size(): 9, intVector.capacity(): 16
intVector.size(): 10, intVector.capacity(): 16
intVector.size(): 11, intVector.capacity(): 16
intVector.size(): 12, intVector.capacity(): 16
intVector.size(): 13, intVector.capacity(): 16
intVector.size(): 14, intVector.capacity(): 16
intVector.size(): 15, intVector.capacity(): 16
intVector.size(): 16, intVector.capacity(): 16
intVector.size(): 17, intVector.capacity(): 32
intVector.size(): 18, intVector.capacity(): 32
intVector.size(): 19, intVector.capacity(): 32
intVector.size(): 20, intVector.capacity(): 32
intVector.size(): 1, intVector.capacity(): 20
intVector.size(): 2, intVector.capacity(): 20
intVector.size(): 3, intVector.capacity(): 20
intVector.size(): 4, intVector.capacity(): 20
intVector.size(): 5, intVector.capacity(): 20
intVector.size(): 6, intVector.capacity(): 20
intVector.size(): 7, intVector.capacity(): 20
intVector.size(): 8, intVector.capacity(): 20
intVector.size(): 9, intVector.capacity(): 20
intVector.size(): 10, intVector.capacity(): 20
intVector.size(): 11, intVector.capacity(): 20
intVector.size(): 12, intVector.capacity(): 20
intVector.size(): 13, intVector.capacity(): 20
intVector.size(): 14, intVector.capacity(): 20
intVector.size(): 15, intVector.capacity(): 20
intVector.size(): 16, intVector.capacity(): 20
intVector.size(): 17, intVector.capacity(): 20
intVector.size(): 18, intVector.capacity(): 20
intVector.size(): 19, intVector.capacity(): 20
intVector.size(): 20, intVector.capacity(): 20
without reserve, cost 259ms
with reserve, cost 204ms



总结

由执行结果可以看出:

  • 当vector内存不足时,新分配内存空间大小是当前的2倍。
  • reserve更大一块空间之后,capacity将等于预留的空间大小,但size()是实时的当前元素个数。
  • 在vector创建时候如果合理预留足够的空间给即将加入的元素,可以避免容量不够的时候重新分配内存,以及在新内存中通过拷贝构造函数实现数据的迁移。
  • 本文例程中vector中的数据类型是内置int,在真正使用场景中,vector有可能存放自定义类型的数据,此时重新分配内存伴随的数据拷贝会引入潜在的更大消耗。

reserve的合理使用是很好的习惯,但如果reserve的空间大小小于当前大小,reserve被视为无效;如果reserve的空间过大,会造成内存浪费。所以预留空间大小还需要斟酌才能发挥预期的作用。

此外,这边博客主要针对vecotr进行讨论,其实对于string也有类似的优化效果。



版权声明:本文为weixin_39703605原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。