STL中Vector的内存分配机制

来源:互联网 发布:j罗 皇马 数据 编辑:程序博客网 时间:2024/04/30 09:04
一些好的公司校园招聘过程中(包括笔试、面试环节),经常会涉及到STL中vector的使用(主要是笔试)及其性能(面试)的分析。今天看了下相关文章,也写了几个小的测试程序跑了跑。算是总结下,希望对需要的人有帮助。

关于vector,简单地讲就是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时会自动申请另一片更大的空间,然后把原有数据拷贝过去,接着释放原来的那片空间;当释放或者说是删除里面的数据时,其存储空间并不会释放,仅仅只是清空了里面的数据。接下来,我会详细地说说这些。

备注:本文的相关程序都是在windows 7+VS2008环境下测试。

一、首先,看看vector的内存分配机制:

[cpp] view plain copy
  1. vector<int> arr;  
  2. ofstream wf("1.txt");  
  3. for(int i=0;i<100;++i)  
  4. {  
  5.     arr.push_back(i);  
  6.     wf<<"capacity="<<arr.capacity()<<",size="<<arr.size()<<end;  
  7. }  
  8. wf.close();  
capacity()返回的是当前vector对象缓冲区(后面的对vector维护的内存空间皆称为缓冲区)实际申请的空间大小,而size()返回的是当前对象缓冲区中存储数据的个数,capacity永远是大于等于size的,当size和capacity相等时继续添加数据时vector会扩容。

再来看看1.txt中的数据:

capacity=1,size=1
capacity=2,size=2
capacity=3,size=3
capacity=4,size=4
capacity=6,size=5
capacity=6,size=6
capacity=9,size=7
capacity=9,size=8
capacity=9,size=9
capacity=13,size=10
capacity=13,size=11
capacity=13,size=12
capacity=13,size=13
capacity=19,size=14
capacity=19,size=15
capacity=19,size=16
capacity=19,size=17
capacity=19,size=18
capacity=19,size=19
capacity=28,size=20
capacity=28,size=21
capacity=28,size=22
capacity=28,size=23
capacity=28,size=24
capacity=28,size=25
capacity=28,size=26
capacity=28,size=27
capacity=28,size=28
capacity=42,size=29
capacity=42,size=30
capacity=42,size=31
capacity=42,size=32
capacity=42,size=33
capacity=42,size=34
capacity=42,size=35
capacity=42,size=36
capacity=42,size=37
capacity=42,size=38
capacity=42,size=39
capacity=42,size=40
capacity=42,size=41
capacity=42,size=42
capacity=63,size=43
capacity=63,size=44
capacity=63,size=45
capacity=63,size=46
capacity=63,size=47
capacity=63,size=48
capacity=63,size=49
capacity=63,size=50
capacity=63,size=51
capacity=63,size=52
capacity=63,size=53
capacity=63,size=54
capacity=63,size=55
capacity=63,size=56
capacity=63,size=57
capacity=63,size=58
capacity=63,size=59
capacity=63,size=60
capacity=63,size=61
capacity=63,size=62
capacity=63,size=63
capacity=94,size=64
capacity=94,size=65
capacity=94,size=66
capacity=94,size=67
capacity=94,size=68
capacity=94,size=69
capacity=94,size=70
capacity=94,size=71
capacity=94,size=72
capacity=94,size=73
capacity=94,size=74
capacity=94,size=75
capacity=94,size=76
capacity=94,size=77
capacity=94,size=78
capacity=94,size=79
capacity=94,size=80
capacity=94,size=81
capacity=94,size=82
capacity=94,size=83
capacity=94,size=84
capacity=94,size=85
capacity=94,size=86
capacity=94,size=87
capacity=94,size=88
capacity=94,size=89
capacity=94,size=90
capacity=94,size=91
capacity=94,size=92
capacity=94,size=93
capacity=94,size=94
capacity=141,size=95
capacity=141,size=96
capacity=141,size=97
capacity=141,size=98
capacity=141,size=99
capacity=141,size=100

数据有点多,提炼下就是这样的:

capacity=1
capacity=2
capacity=3
capacity=4
capacity=6
capacity=9
capacity=13
capacity=19
capacity=28
capacity=42
capacity=63
capacity=94
capacity=141

看出其中的规律没?对,就是每次扩容都是增加当前空间的50%(第一次除外);

9+9/2=13;13+13/2=19;19+19/2=28……

其实STL的源码我们都可以看到的,具体就在你说安装的编译器目录下,例如,我的VS2008是在:安装目录\VC\include下面。你也可以在VS中直接选中#include <vector>右键打开。当然了,windows上的STL源码都是P.J. Plauger写的(PS:很牛B的博士,百度你就知道),大家都说可读性极差,我也这么认为,我们这些菜鸟还是看GCC中的STL源码吧。

\VC\include\vector中是这样扩容的:

[cpp] view plain copy
  1. if (_Count == 0)//这里进行了判断,但是什么都不做,不知道为什么???????  
  2.             ;  
  3.         else if (max_size() - size() < _Count)//编译器可以申请的最大容量也装不下,抛出异常_THROW(length_error, "vector<T> too long");  
  4.             _Xlen();    // result too long  
  5.         else if (_Capacity < size() + _Count)//当前空间不足,需要扩容  
  6.             {   // not enough room, reallocate  
  7.             _Capacity = max_size() - _Capacity / 2 < _Capacity  
  8.                 ? 0 : _Capacity + _Capacity / 2;    // try to grow by 50%,扩容50%  
  9.             if (_Capacity < size() + _Count)//扩容50%后依然不够容下,则使容量等于当前数据个数加上新增数据个数  
  10.                 _Capacity = size() + _Count;  
  11.             pointer _Newvec = this->_Alval.allocate(_Capacity);//申请新的空间  
  12.             pointer _Ptr = _Newvec;  
  13.   
  14.             _TRY_BEGIN  
  15.             _Ptr = _Umove(_Myfirst, _VEC_ITER_BASE(_Where),  
  16.                 _Newvec);   // copy prefix  <span style="white-space:pre">    </span>//拷贝原有数据到新的内存中  
  17.             _Ptr = _Ucopy(_First, _Last, _Ptr); // add new stuff<span style="white-space:pre">    </span>//拷贝新增数据到新的内存的后面  
  18.             _Umove(_VEC_ITER_BASE(_Where), _Mylast, _Ptr);  // copy suffix  
  19.             _CATCH_ALL  
  20.             _Destroy(_Newvec, _Ptr);  
  21.             this->_Alval.deallocate(_Newvec, _Capacity);//释放原来申请的内存  
  22.             _RERAISE;  
  23.             _CATCH_END  
对的,就是每次扩容50%。至于删除容器中数据的时候,缓冲区大小并不会改变,仅仅只是清楚了其中的数据,只有在析构函数调用的时候vector才会自动释放缓冲区。

看看它的析构代码:

[cpp] view plain copy
  1. ~vector()  
  2.     {   // destroy the object  
  3.     _Tidy();  
  4.     }  
[cpp] view plain copy
  1. void _Tidy()  
  2. {// free all storage  
  3. if (_Myfirst != 0)  
  4. {// something to free, destroy and deallocate it  
  5.   
  6.   
  7.  #if _HAS_ITERATOR_DEBUGGING  
  8. this->_Orphan_all();  
  9.  #endif /* _HAS_ITERATOR_DEBUGGING */  
  10.   
  11.   
  12. _Destroy(_Myfirst, _Mylast);//应该是销毁vector中的每一个元素吧  
  13. this->_Alval.deallocate(_Myfirst, _Myend - _Myfirst);//释放缓冲区的空间  
  14. }  
  15. _Myfirst = 0, _Mylast = 0, _Myend = 0;//指针全部归零  
  16. }  
那么,我们可以在需要的时候强制释放缓冲区不?

二、如何强制释放vector的缓冲区:

答案是可以的,既然析构时会释放空间,那么我们就可以换个方式调用析构函数。

[cpp] view plain copy
  1. //  //方法一、  
[cpp] view plain copy
  1.     vector<int>().swap(arr); //交换后  
  2. //方法二、  
  3. {  
  4.     vector<int> temp;//临时对象未初始化,其缓冲区大小为0,没有数据  
  5.     arr.swap(temp);//与我们的对象交换数据,arr的缓冲区就没了。  
  6. }//临时变量会被析构,temp调用vector析构函数释放空间  
三、如何使用提高性能:

为了比较,我们用了三种方式来把100个数据存入vector中,分别是:1、直接每次push_back();2、使用resize()提前分配100个空间,然后push_back;3、使用reserve提前分配100个存储空间。MSDN中,这两个个函数的说明分别是:

reserveReserves a minimum length of storage for a vector object, allocating space if necessary.

resizeSpecifies a new size for a vector.

在这里我们初始化的时候使用感觉好像是差不多。

[cpp] view plain copy
  1. clock_t start=clock();  
  2.     for(int num=0;num<10000;++num)  
  3.     {  
  4.         vector<int> v1;  
  5.         for(int i=0;i<100;++i)  
  6.             v1.push_back(i);  
  7.     }  
  8.     cout<<"直接push循环10000次用时:"<<clock()-start<<endl;  
  9.     start=clock();  
  10.     for(int num=0;num<10000;++num)  
  11.     {  
  12.         vector<int> v2;  
  13.         v2.resize(100);  
  14.         for(int i=0;i<100;++i)  
  15.             v2.push_back(i);  
  16.     }  
  17.     cout<<"先resize预设大小再push循环10000次用时:"<<clock()-start<<endl;  
  18.     start=clock();  
  19.     for(int num=0;num<10000;++num)  
  20.     {  
  21.         vector<int> v3;  
  22.         v3.reserve(100);  
  23.         for(int i=0;i<100;++i)  
  24.             v3.push_back(i);  
  25.     }  
  26.     cout<<"先reserve预设大小再push循环10000次用时:"<<clock()-start<<endl;  

结果却不尽相同


reserve只是保持一个最小的空间大小,而resize则是对缓冲区进行重新分配,里面涉及到的判断和内存处理比较多,当然了在这里由于最初都是空的所以差别不大。

两者的区别查看:vector::reserve和vector::resize的区别。

由此可见,对于数据数目可以确定的时候,先预设空间大小是很有必要的。直接push_back数据频繁移动很是耗时(当然了,数据小的可以忽略的)。


真个测试程序的完整代码如下

[cpp] view plain copy
  1. #include "stdafx.h"  
  2. #include "btree.h"  
  3. #include <vector>  
  4. #include <iostream>  
  5. #include <Windows.h>  
  6. #include <fstream>  
  7. #include <time.h>  
  8. using std::ofstream;  
  9. using std::cout;  
  10. using std::endl;  
  11. using std::vector;  
  12. int _tmain(int argc, _TCHAR* argv[])  
  13. {  
  14.     /************************************************************************/  
  15.     /* vector如何强制释放内存空间                                         */  
  16.     /* 默认只有析构时才会释放                                              */  
  17.     /************************************************************************/  
  18.     vector<int> arr;  
  19.     cout<<"默认情况未初始化时,capacity="<<arr.capacity()<<endl;  
  20.     arr.resize(100,100);  
  21.     arr.reserve(50);  
  22.     arr.resize(50);  
  23.     cout<<"现在,capacity="<<arr.capacity()<<endl;  
  24.     vector<int>::iterator itor=arr.begin()+10;  
  25.     arr.erase(arr.begin(),itor);  
  26.     cout<<"capacity="<<arr.capacity()<<",size="<<arr.size()<<endl;  
  27. //  //方法一、  
  28.     vector<int>().swap(arr); //强制释放空间  
  29.     //方法二、  
  30.     {  
  31.         vector<int> temp;  
  32.         arr.swap(temp);  
  33.     }//临时变量会被析构  
  34.     cout<<"capacity="<<arr.capacity()<<",size="<<arr.size()<<endl;  
  35.     clock_t start=clock();  
  36.     for(int num=0;num<10000;++num)  
  37.     {  
  38.         vector<int> v1;  
  39.         for(int i=0;i<100;++i)  
  40.             v1.push_back(i);  
  41.     }  
  42.     cout<<"直接push循环10000次用时:"<<clock()-start<<endl;  
  43.     start=clock();  
  44.     for(int num=0;num<10000;++num)  
  45.     {  
  46.         vector<int> v2;  
  47.         v2.resize(100);  
  48.         for(int i=0;i<100;++i)  
  49.             v2[i] = i;  
  50.     }  
  51.     cout<<"先resize预设大小再push循环10000次用时:"<<clock()-start<<endl;  
  52.     start=clock();  
  53.     for(int num=0;num<10000;++num)  
  54.     {  
  55.         vector<int> v3;  
  56.         v3.reserve(100);  
  57.         for(int i=0;i<100;++i)  
  58.             v3.push_back(i);  
  59.     }  
  60.     cout<<"先reserve预设大小再push循环10000次用时:"<<clock()-start<<endl;  
  61.     vector<int> v4;  
  62.     ofstream wf("2.txt");  
  63.     int nFlag=v4.capacity();  
  64.     for(int i=0;i<100;++i)  
  65.     {  
  66.         v4.push_back(i);  
  67.         if(nFlag!=v4.capacity())  
  68.         {  
  69.             nFlag=v4.capacity();  
  70.             cout<<"new buffer size="<<nFlag<<endl;  
  71.             wf<<"capacity="<<nFlag<<endl;  
  72.         }  
  73.     }  
  74.     wf.close();  
  75.     cout<<"max_size="<<arr.max_size()<<endl;  
  76.     return 0;  
  77. }  

参考了一些前辈的文章,能力有限,欢迎指教,相互学习。
0 0
原创粉丝点击