readme
STL容器,例如vector, deque, string, map, set等等,是几乎每个c++程序员离不开的工具。STL容器之所以深受c++程序员喜爱,重要原因之一就是它会自动管理内存,不需要程序员手动开辟或释放内存。有利必有弊,STL容器申请的内存不会用完立即释放,通常会保留在内存池中,留给下次复用。某种意义讲这是一种优化,毕竟内存申请释放也是一种消耗,再结合内存碎片等因素,有时甚至是一种不能忽略的消耗。但是对于内存使用敏感的运行环境,STL的这种“优化”行为是不可接受的。这篇博客介绍一种方法主动释放这些STL不愿主动释放的内存希望,可以帮到看到的人。
现状
针对这一问题的思考来自一个项目的调优过程。我们将一个复杂流程采用多线程并发的方式并发处理,恶化了STL内存不释放的问题。客观讲笔者对STL源码了解并不深刻,无法解释多线程与STL内存不释放之间的联系。这里只讨论主要问题。
另外还需要说明一点,我们的程序运行在docker容器中,如果容器内进程占用内存超过阈值,容器管理系统将kill业务进程。如果程序运行于物理机上,猜想当STL开辟内存失败后会被动释放之前保留的内存。
经过一系列调研,发现目前比较流行的方法主要分两种:
- swap。大致原理是让目标容器与空实例交换。例如下列代码:
vector<string> testVec;
/*其他逻辑*/
vector<string>().swap(testVec);
经过大量测试,发现通过swap释放内存并不可靠。
- 自己设计并实现STL内存分配器(allocator)和内存释放器(deallocator)。这是很彻底的解决方案,但是保证代码的可移植性和足够高的效率将消耗开发人员大量精力。而且需要比较扎实的STL底层技术积累。
另一种方式
经过一段时间的焦头烂额,我们发现了新的方法,malloc_trim()。
在linux man-pages里面的描述中可以看出,这个api会释放堆中的内存,而且很多时候在free()的时候会自动被调用。看上去和我们的场景十分相似,对STL的使用不需要调用free(),或者说没有机会调用free(),大多数程序员也根本不知道STL容器何时会调用free()。
经过线上试验,发现malloc_trim()也确实解决了内存不释放的问题。接下来我用很简单的程序展现malloc_trim的作用。
测试程序
//common.h
#ifndef _COMMON_H_
#define _COMMON_H_
#include <string>
#include <stdio.h>
#include <memory>
#include <stdlib.h>
#include <unistd.h>
#include <utility>
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>
using namespace std;
#endif
//malloc_trim.cpp
#include "common.h"
int main(void){
string testStr(1024 * 1024, 'a');
{
vector<pair<string, string>> testVec(1024 * 1024);
for (size_t i = 0; i < 1024 * 1024; ++i)
{
testVec.push_back(make_pair(testStr, testStr));
}
getchar();
}
{
vector<pair<string, string>> testVec(1024 * 1024);
for (size_t i = 0; i < 1024 * 1024; ++i)
{
testVec.push_back(make_pair(testStr, testStr));
}
getchar();
}
printf("Have a look at how much memory used.");
getchar();
malloc_trim(0);
printf("How abort now.");
getchar();
return 0;
}
编译
g++ -o malloc_trim malloc_trim.cpp -O0 -g -Wall -std=c++11
测试程序说明
在输出“Have a look at how much memory used.”的时候,两个独立作用域中的testVec都已经结束了生命周期,按照c语言的习惯,它们占用的内存应该被释放,但是STL并没有释放。
在输出“How abort now.”的时候,程序占用的内存被释放,达到了目的。