在一个项目中,需要在服务端保存玩家的录像回放数据,采用vector/map容器暂存了下发的消息数据,等待游戏结束后就将其写入文件,然后用clear清除掉这块缓存。

游戏上线了之后,发现其占用的内存一直上升,搜寻日志后发现,每局结束后回放占用的空间并没有释放掉,随着房间一直保留。也就是假设一共1000个房间,每个房间都有玩家游戏过后,就会有一千份回放空间没释放。

瞬 · 间 · 爆 · 炸

普遍说法是vector的clear并没有真正的释放内存,仅仅只是调用了元素的析构函数,然后调整了vector内部存取的位置。实际可以根据以下代码来观察:

#include<vector>
using namespace std;struct TestClass
{TestClass(){m_value=3;puts("OK1");}TestClass(int value){m_value = value;puts("OK2");}TestClass(const TestClass &tmp){this->m_value = tmp.m_value;puts("OK3");}~TestClass(){printf("free TestClass , m_value = %d\n",m_value);}int m_value;
};int main()
{TestClass testTmp(15);{vector<TestClass> vctTest;vctTest.push_back(testTmp);puts("PUSH OVER");vctTest.clear();puts("CLEAR OVER");printf("capacity = %d\n",vctTest.capacity());printf("test value beyond of limits: %d\n",vctTest[0].m_value);printf("test value beyond of limits: %d\n",vctTest[1].m_value);}puts("TEST OVER");return 0;
}

其输出的结果是

大致顺序为:

1.调用了带参构造函数,testTmp初始化完成。(输出OK2)

2.在vctTest容器的push_back时,调用拷贝构造函数新生成了一个对象,将其压入vector。(输出OK3)

3.在vctTest容器的clear中,对其中的元素调用了析构函数。(输出free TestClass , XXXX)

4.在clear完之后仍然在内存越界的边缘试探:(输出test value beyond of limits: XXXX)

5.出了定义域范围,释放vector所占空间。(输出TEST OVER)

6.main函数结束,对testTemp调用析构函数,并释放空间。(输出free TestClass , XXXX)

从上可以观察出:clear时,析构函数的确是调用了,而vector的真实大小capacity仍然是1。

而对于vector来说:其内存结构是整段连续的,每当大小不够时,就重新申请一块更大的空间,并将原数据复制过去,最后释放原空间。而这块更大的空间大小,根据vector自身策略而定,避免频繁申请内存并且复制数据,导致耗时大量增加。自然,为了减少clear之后再次扩容的消耗,所以vector选择不释放这块内存,留待之后重复利用。如果真正想要释放其内存,vector也提供了对应的方法。

然后又到了"网上普遍做法"环节:

1.用swap函数交换一个空vector来达到真正释放内存的目的。

//vctTest.swap(vector<TestClass>());
vector<TestClass>().swap(vctTest); 

注释掉的那种在一般情况下会报错,因为C++11不允许将临时变量赋给非const左值引用。(但VS2015可以)

具体解释请看Virtual_Func博主的这篇文章。

交换完之后,等出了定义域范围,临时变量就会被释放掉。

2.在clear完之后用shrink_to_fit函数对vector进行大小重调。

vctTest.shrink_to_fit();

不过需要C++11的支持。按照文档的说明,只是请求释放内存,标准库并不保证退换内存。

最终采用的是第一种方案,测试了一下内存的消耗确实稳定了,游戏结束后相关内存会释放掉,不会一直处于上涨的状态。


然鹅,事情并没有结束。因为运行一段时间后,发现内存虽然没有一直涨,但也保持在了一个比较高的消耗上。观察了一段时间的内存占用,发现每次游戏开始内存上涨,但游戏结束后并没有全部释放。反复N次之后,内存占用上升到一定值不再增涨,但也几乎看不出波动。于是推测应该是内存被什么东西缓存导致的。

一番查找后,发现原因在于之前所说的swap操作对map容器并没有完全起作用。通过小程序测试,往map中塞了40MB的数据后,再进行释放,其仍然占用了10MB的内存空间。这里的释放,是指用swap操作,或者是已运行到临时变量map的定义域之外。两种释放最终结果都是一样的,均有内存空间未被释放。(当然,不同机器不同情况下未释放内存比例可能不同,但应该都存在未释放的)

再联系网上的解释:STL map 内存释放

我的结论是map容器会根据策略留下一定比例的内存留待之后使用,且这块内存是无法通过swap或者析构map来释放的。而在之后的测试中,也发现在释放旧map后仍占用10MB的情况下,再去向新map中塞小于10MB的数据时,其并不会再去申请新的内存空间,而是直接使用了这10MB空间(无论新旧两个map是否同一结构,内存都是可以重复利用的)。

目前来说,暂时还未找到释放的方法,先考虑调整不用map结构,或者是调整map中存储的元素结构,缩小其内存占用。

(不过上面这位网友的做法说不定是一个途径。。?)

另外有一个发现是,即使是vector,如果是嵌套结构(即vector < vector<int> >这种结构),对其进行swap操作也是无法释放其全部内存占用的,无论是直接整个vector swap,或者遍历vector内部对其子vector逐个swap后再将总vector进行swap,都是一样的结果。

测试用代码:

#include <stdio.h>
#include <map>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
using namespace std;#define MAXLEN 3072// 定义数据流结构
struct MsgInfo
{char message[MAXLEN];MsgInfo() {}
};// 桌子结构
struct Table
{map<int,int> m_mapInitXY;vector< vector<MsgInfo> > m_vtInitMsg;// 通过swap释放内存 void Clear(){map<int,int>().swap(m_mapInitXY);for (int i = 0; i < m_vtInitMsg.size(); i++){vector<MsgInfo>().swap(m_vtInitMsg[i]);}vector< vector<MsgInfo> >().swap(m_vtInitMsg);}
};int main()
{srand(GetTickCount());map<int,bool> m_tableused;vector<int> m_tableList;vector<int> m_tableMsgCount;// 一共测试消息条数 int circleTimes = 10000000;// 当前桌子使用数 int tableCount = 0;// 结束标志 int XYEnd = 333;// 修改持续次数 int changeTimes = 0;// 期望桌子使用数 int maxTableUsed = 3;// 桌子上限 (顺便用作其他一些上限值)int tableLimit = 1000;Table *pTables = new Table[tableLimit];time_t lasttime = time(NULL);while(circleTimes--){// 每100000条消息可修改一次期望桌子使用数// changetimes代表后N次修改均使用当前值 if (circleTimes % 100000 == 0){if (changeTimes == 0){printf("cost time = %d\n", time(NULL) - lasttime);printf("Input MaxTableUse ChangeTimes:");scanf("%d %d",&maxTableUsed,&changeTimes);lasttime = time(NULL);}changeTimes--;}// 随机获取将要使用的桌子 int tableid = 0;int listpos = 0;if (tableCount >= maxTableUsed){listpos = rand() % tableCount;tableid = m_tableList[listpos];}else{tableid = rand() % tableLimit;}// 新桌子 if (m_tableused[tableid] == false){m_tableused[tableid] = true;tableCount++;m_tableList.push_back(tableid);m_tableMsgCount.push_back(0);listpos = m_tableList.size() - 1;printf("CreateTable : %d\n",m_tableList[listpos]);}else{for (int i = 0; i < m_tableList.size(); i++){if (m_tableList[i] == tableid){listpos = i;}}}// 保存数据 Table &tableNow = pTables[tableid];// 随机协议号 int XYID = rand() % tableLimit;MsgInfo infoTemp;if (tableNow.m_mapInitXY.find(XYID) == tableNow.m_mapInitXY.end()){tableNow.m_mapInitXY[XYID] = tableNow.m_vtInitMsg.size();tableNow.m_vtInitMsg.push_back(vector<MsgInfo>());}int XYPos = tableNow.m_mapInitXY[XYID];tableNow.m_vtInitMsg[XYPos].push_back(infoTemp);m_tableMsgCount[listpos]++;// 消息数达到一定值后或者遇到结束标志,清除数据 if (m_tableMsgCount[listpos] > tableLimit || XYID == XYEnd){tableNow.Clear();printf("FreeTable : %d\n",m_tableList[listpos]);tableCount--;m_tableused[tableid] = false;m_tableMsgCount.erase(m_tableMsgCount.begin() + listpos);m_tableList.erase(m_tableList.begin() + listpos);}}puts("End!");getchar();
} /*
sample Input:
50 2
40 2
30 2
20 2
10 2
1 2
*/

果然还是得看看STL源码剖析啊。

STL容器 内存释放相关推荐

  1. 容器内存释放问题(STL新手笔记)

    最近看了下STL,用的过程中有一些体会需要记一下. 容器的空间申请和基本函数操作,以及algorithm等都比较好理解,用起来也很方便,比较关键的是容器元素包含指针时,空间的申请和释放问题,这个觉得S ...

  2. STL容器存储的内容动态分配情况下的内存管理

    主要分两种情况:存储的内容是指针:存储的内容是实际对象. 看以下两段代码, typedef pair<VirObjTYPE, std::list<CheckID>*> VirO ...

  3. C++ 中 map 容器的内存释放机制及内存碎片管理

    C++ 中 map 容器的内存释放机制及内存碎片管理 C++ 中的容器很好用,比如 vector, map 等,可以动态扩容,自己管理内存,不用用户关心,但是在某些极端情况下,如果内存比较紧张的情况下 ...

  4. c++STL容器的string

    STL容器的string String概念 string是一个类, char*是一个指向字符的指针. string不用考虑内存释放和越界. string提供了一系列的字符串操作函数 string的构造 ...

  5. STL容器底层数据结构的实现

    C++ STL 的实现: 1.vector      底层数据结构为数组 ,支持快速随机访问 2.list            底层数据结构为双向链表,支持快速增删 3.deque       底层 ...

  6. vector的内存释放

    1. vector容器的内存自增长 与其他容器不同,其内存空间只会增长,不会减小.先来看看"C++ Primer"中怎么说:为了支持快速的随机访问,vector容器的元素以连续方式 ...

  7. [转]STL的内存分配器

    题记:内存管理一直是C/C++程序的红灯区.关于内存管理的话题,大致有两类侧重点,一类是内存的正确使用,例如C++中new和delete应该成对出现,用RAII技巧管理内存资源,auto_ptr等方面 ...

  8. STL容器的底层数据结构

    本文部分内容转自此博客 目录 vector list deque stack queue heap priority_queue set map multiset/multimap 哈希表hashta ...

  9. c++之STl容器-string

    目录 容器的分类 string string的概念 string的初始化 string的遍历 string的一些基本操作 char*类型和string类型互转 字符串的连接 字符串的查找和替换 str ...

最新文章

  1. golang错误处理
  2. 《12个球问题》分析
  3. 肝了一个月,终于搞到了 30 页的 Python 进阶面试题
  4. 【数据结构与算法】之深入解析“灯泡开关”的求解思路与算法示例
  5. 【leetcode】Set Matrix Zeroes(middle)
  6. python后端数据发送到前端_Python Django 前后端数据交互 之 后端向前端发送数据...
  7. Silverlight 3.0 Isolated Storage 独立存储空间
  8. Java中string.equalsIgnoreCase(0)与0.equalsIgnoreCase(string)的区别:
  9. 如何搭建高性能视频网站
  10. springboot-29-security(二)用户角色权限控制
  11. iOS类别(Category)和扩展(Extension,匿名类)
  12. python科学计算-01程序包和API简介
  13. CES直击:戴尔连发多款ALIENWARE与XPS新品
  14. 华北水利水电大学历年c语言试题,一百题C语言试题
  15. 【分享】新品TI AM5708开发板!DSP+ARM异构多核!相比OMAP-L138,性能升级;相比AM5728,成本优化、功耗更低!
  16. 宝德自强PT620Z1
  17. opencv-python-仿射变换-图片拉伸成平行四边形
  18. STM32驱动WS2812B-2020 RGB彩灯(一)
  19. Linux Ubuntu 虚拟机不能连网、Linux Ubuntu 虚拟机怎么连网
  20. Win10右键文件无响应崩溃

热门文章

  1. 二叉树的编程与实现(C语言)
  2. Windows操作系统是怎样被开发出来的?
  3. 侧边栏php,WordPress中用于创建以及获取侧边栏的PHP函数讲解
  4. html侧边栏如何shubie,div+css侧边栏怎么写
  5. Guitar Pro 8.0最详细全面的更新内容及全部功能介绍
  6. JConsole远程连接
  7. 为什么说AI创业不是4、5个人的团队就能搞定的事
  8. Spring Cloud Streams Messaging消息驱动微服务实践
  9. 手机中geetest是什么文件_手机中的英文文件夹都表示什么意思?哪些是可以删除的?...
  10. 小程序数据回传,刷新父界面数据