在一个项目中,需要在服务端保存玩家的录像回放数据,采用vector/map容器暂存了下发的消息数据,等待游戏结束后就将其写入文件,然后用clear清除掉这块缓存。
游戏上线了之后,发现其占用的内存一直上升,搜寻日志后发现,每局结束后回放占用的空间并没有释放掉,随着房间一直保留。也就是假设一共1000个房间,每个房间都有玩家游戏过后,就会有一千份回放空间没释放。
普遍说法是vector的clear并没有真正的释放内存,仅仅只是调用了元素的析构函数,然后调整了vector内部存取的位置。实际可以根据以下代码来观察:
#include
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 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());
vector().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
测试用代码:
#include
#include
果然还是得看看STL源码剖析啊。