转自:https://blog.csdn.net/mfcing/article/details/8746256

一些好的公司校园招聘过程中(包括笔试、面试环节),经常会涉及到STL中vector的使用(主要是笔试)及其性能(面试)的分析。今天看了下相关文章,也写了几个小的测试程序跑了跑。算是总结下,希望对需要的人有帮助。

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

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

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

  1. vector<int> arr;
  2. ofstream wf("1.txt");
  3. for(int i=;i<;++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中是这样扩容的:

  1. if (_Count == )//这里进行了判断,但是什么都不做,不知道为什么???????
  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 / < _Capacity
  8. ? : _Capacity + _Capacity / ; // 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才会自动释放缓冲区。

看看它的析构代码:

  1. ~vector()
  2. { // destroy the object
  3. _Tidy();
  4. }
  1. void _Tidy()
  2. {// free all storage
  3. if (_Myfirst != )
  4. {// something to free, destroy and deallocate it
  5.  
  6.  #if _HAS_ITERATOR_DEBUGGING
  7. this->_Orphan_all();
  8.  #endif /* _HAS_ITERATOR_DEBUGGING */
  9.  
  10. _Destroy(_Myfirst, _Mylast);//应该是销毁vector中的每一个元素吧
  11. this->_Alval.deallocate(_Myfirst, _Myend - _Myfirst);//释放缓冲区的空间
  12. }
  13. _Myfirst = , _Mylast = , _Myend = ;//指针全部归零
  14. }

那么,我们可以在需要的时候强制释放缓冲区不?
答案是可以的,既然析构时会释放空间,那么我们就可以换个方式调用析构函数。

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

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

三、如何使用提高性能

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

reserve: Reserves a minimum length of storage for a vector object, allocating space if necessary.

resize:    Specifies a new size for a vector.

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

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

结果如下所示

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

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

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

整个程序的完整代码

  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(,);
  21. arr.reserve();
  22. arr.resize();
  23. cout<<"现在,capacity="<<arr.capacity()<<endl;
  24. vector<int>::iterator itor=arr.begin()+;
  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=;num<;++num)
  37. {
  38. vector<int> v1;
  39. for(int i=;i<;++i)
  40. v1.push_back(i);
  41. }
  42. cout<<"直接push循环10000次用时:"<<clock()-start<<endl;
  43. start=clock();
  44. for(int num=;num<;++num)
  45. {
  46. vector<int> v2;
  47. v2.resize();
  48. for(int i=;i<;++i)
  49. v2[i] = i;
  50. }
  51. cout<<"先resize预设大小再push循环10000次用时:"<<clock()-start<<endl;
  52. start=clock();
  53. for(int num=;num<;++num)
  54. {
  55. vector<int> v3;
  56. v3.reserve();
  57. for(int i=;i<;++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=;i<;++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 ;
  77. }

【C++】vector内存机制和性能分析的更多相关文章

  1. C++ STL中vector的内存机制和性能分析

    vecotr是动态数组,顾名思义他可以动态的增加自己的长度. 内存机制: 但是怎样的增加自己的长度? vector有两个函数一个是capacity()返回内存空间即缓冲区的大小,另一个是size()返 ...

  2. jstack(查看线程)、jmap(查看内存)和jstat(性能分析)

    公司内部同事分享的一篇文章 周末看到一个用jstack查看死锁的例子.昨天晚上总结了一下jstack(查看线程).jmap(查看内存)和jstat(性能分析)命令.供大家参考 1.Jstack 1.1 ...

  3. jstack(查看线程)、jmap(查看内存)和jstat(性能分析)命令

    jstack(查看线程).jmap(查看内存)和jstat(性能分析)命令   公司内部同事分享的一篇文章 周末看到一个用jstack查看死锁的例子.昨天晚上总结了一下jstack(查看线程).jma ...

  4. 命令:jstack(查看线程)、jmap(查看内存)和jstat(性能分析)命令

    命令:jstack(查看线程).jmap(查看内存)和jstat(性能分析)命令 这些命令 必须 在 linux jdk bin 路径 下执行 eq: ./jstack 10303 即可  如果想把 ...

  5. SQL Server ->> Memory Allocation Mechanism and Performance Analysis(内存分配机制与性能分析)之 -- Minimum server memory与Maximum server memory

    Minimum server memory与Maximum server memory是SQL Server下配置实例级别最大和最小可用内存(注意不等于物理内存)的服务器配置选项.它们是管理SQL S ...

  6. valgrind和Kcachegrind性能分析工具详解

    一.valgrind介绍 valgrind是运行在Linux上的一套基于仿真技术的程序调试和分析工具,用于构建动态分析工具的装备性框架.它包括一个工具集,每个工具执行某种类型的调试.分析或类似的任务, ...

  7. .NET内存性能分析宝典

    .NET Memory Performance Analysis 知道什么时候该担心,以及在需要担心的时候该怎么做 译者注 **作者信息:Maoni Stephens ** - 微软架构师,负责.NE ...

  8. Android内存机制分析1——了解Android堆和栈

    //----------------------------------------------------------------------------------- Android内存机制分析1 ...

  9. SQL Server内存性能分析

    内存概念: Working Set = Private Bytes + Shared Memory Working Set:某个进程的地址空间中,存放在物理内存的那一部分 Private Bytes: ...

随机推荐

  1. 做h5动画会用到的一个很好的缓动算法库

    http://www.zhangxinxu.com/wordpress/2016/12/how-use-tween-js-animation-easing/

  2. 数据分析库之matplotlib

    一.Matplotlib基础知识 Matplotlib中的基本图表包括的元素 x轴和y轴 axis 水平和垂直的轴线 轴标签 axisLabel 水平和垂直的轴标签 x轴和y轴刻度 tick 刻度标示 ...

  3. ZCRM_AU_MAIL

    FUNCTION ZCRM_AU_MAIL. *"---------------------------------------------------------------------- ...

  4. spring boot多模块项目找不到类

    项目结构 mapper依赖pojo, service依赖mapper和pojo portal依赖pojo和service. 全都是maven模块 <groupId>com.haitian& ...

  5. Android动画-View动画

    View动画 Android动画分为三类:View动画,帧动画,和属性动画.帧动画也是View动画的一种. View动画的作用对象是View,之所以强调这一点是因为其作用对象有别于Android的另一 ...

  6. 移动rem自适应

    /** * rem计算方式:设计图尺寸px / 100 = 实际rem 例: 100px = 1rem */!function (window) { /* 设计图文档宽度 */ var docWidt ...

  7. python单例(重点)

    单例 目标 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式 设计模式 是 前人工作的总结和提炼,通常,被人们广泛流传的设计模式都是针对 某一特定问题的成熟的 ...

  8. git不添加.idea等IDE配置文件夹

    git不添加.idea等IDE配置文件夹由于.idea(intellJ,pycharm)的IDE文件夹很常出现,建议将gitignore设置为全局. git config --global core. ...

  9. [转] C++ 和 python之间的互相调用

    转载自:https://www.cnblogs.com/apexchu/p/5015961.html 一.Python调用C/C++ 1.Python调用C动态链接库 Python调用C库比较简单,不 ...

  10. UILabel Text显示不同的颜色

    UILabel的text实现不同的颜色. NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithStr ...