首先要说明一点,这个内存池使用时需要注意的,如果想用在长期分配出去的内存,请慎用.
因为假如一个区块被分配完了,只有在这个区块里已分配的内存被完全释放后,这个区块才能重用.
因为当初是设计为网络分包用的内存池.为了效率而采用这个策略的.
发代码之前先简单介绍下内存池的思路.
内存池分256个区块,编号为0~255
区块的结构为:
区块记录了3个信息一个指针
_left_mem是剩余的memory,初始化为区块内存总长
_alloc_org是指向区块有效内存的起始点.初始化为0(整个区块一开始都是可以有效分配出去的)
_need_to_rel是需要释放的内存.初始化为区块内存总长
内存块先简单介绍.后面再详细说实现细节
//区块,用于保存未分配内存(被分割出去) struct _chunk{ int _left_mem; //此chunk剩余长度 int _alloc_org; //指向可用于分配内存快头的指针 int _need_to_rel;//需要释放多少内存 char _mem[1]; //此chunk持有的内存区 };
而分配出去的块用以下结构标识
struct _mem_piece{ int _mp_len; //内存块大小 char _chunk_index; //内存块所属区块的索引 char _mem[1]; //内存块的内存首地址 };
内存池的所有区块可以分为三个区域
无效区:这里的区块已经完全分配完毕,等待释放,释放完成后会到自由区
待分配区:这个区有且只有一个块,分配内存都会从这里抽取
自由区:
下图是内存池的状态:
内存池一开始会处于以下的状态
初始化的时候第一个区块会被初始化.继续分配就会耗尽绿色的待分配块,内存池会把它归入无效区,同时尝试从系统创建一个新的区块并列为待分配块
只有无效区的内存的块被完全释放后才会转到自由区.当然,如果自由区有区块的话,就不需要新建区块,而是直接指定自由区的第一个区块为待分配区块
然后我再来说明一下分配的策略._left_mem一开始就是该区块总内存大小这个不需要多说了.
malloc被调用时:
1.当_left_mem比要求分配的内存多的时候.把当前_alloc_org处分配出去,减少_left_mem和移动_alloc_org.
2.a.当_left_mem不足的时候,_left_mem(剩下的内存)将成为碎片,计算need_to_rel -= _left_mem,此区块将移动到无效区,并且内存池将获取一个新的区块(如果自由区有则直接获取,否则创建一个)
分配出去的时候都执行以下的代码:
则实际上需要的内存大小是sizeof(int) + sizeof(char) + memory size
而客户所得到的内存是从mp->_mem开始的.所以,free回来的内存只要向前移动5个字节就能获取到分配出去的_mem_piece结构了
_mem_piece *mp = (_mem_piece *)&p->_mem[p->_alloc_org]; //内存块真正的大小 mp->_mp_len = need_len; //chunk标识 mp->_chunk_index = _S_cur_valid_index; return mp->_mem;
b.计算need_to_rel -= _left_mem后发现need_to_rel==0则不是移动到无效区而是直接移动到自由区.
free被调用时:
_need_to_rel-=被释放的内存大小,发现_need_to_rel == 0则直接把此块移动到自由区
free调用的时候是这样获取到回来的内存大小的
_mem_piece *pmem = (_mem_piece *)((char *)p - _mem_piece::_mem_piece_struct_size);
_mem_piece记录了这块被分配的内存有多大.直接通过pmem->_mp_len获取,pmem->_chunk_index则记录了属于哪个区块的,这样便可以直接合并回该区块了.
shrink被调用时:
把所有处于自由区的区块释放,调用c标准库的free把内存还给系统,左右瘦身收敛的效果.
_need_to_rel仅仅是一个标记.在调用free的时候如果发现_need_to_rel==0了就直接把这个区块丢到自由区里面.因为最终分配完这个区块以后需要释放的值应该为 区块总内存 - 剩余碎片.所以_need_to_rel一开始就是区块总内存了.在刚刚绿色的待分配被完全耗尽以后,在转入无效区之前,会计算出碎片大小(就是_left_mem)这个时候执行need_to_rel -= _left_mem计算出真实的需释放值.计算完后,程序如果发现need_to_rel==0不会归入无效区而是直接转到自由区
最后总结下这个内存池的优缺点:
优点:
1.分配的时候仅仅移动了指针,内存不足时(即创建新块的时候)调用到c运行库的malloc.如果不算上malloc的话.内存池分配只需要执行少于10个指令.
所以分配速度非常快
2.释放的时候不需要合并这个步骤.很多内存分配器都是在合并上用了很多时间.这个内存池不需要任何合并的动作
3.可以分配任意大小的内存.当然,太大的时候会委托给malloc
缺点:
1.如果分配完一个区块后会造成少量碎片
2.无效区域内的内存只有等完全释放了,这个块才可以重用,所以不适合长期占有内存池的内存.
这个很大限度限制了这个内存池的实用性.
然后贴个测试:
#include "fish_mem_pool.h" #include <boost/timer.hpp> #include <iostream> int main() { boost::timer t; for (int i = 0; i < 1024 * 600; ++i) char *p = (char *)fishlib::fish_mem_pool::malloc(1024); std::cout << "used " << t.elapsed() << " seconds" << std::endl; getchar(); return 0; }
结果:
最后上代码:
配置内存池的头文件:
注意!内存池最大内存大小为256 * _new_chunk_len
这里为 256 * 4096000 = 1000MB
fish_mem_pool_config.h
/* @fish_mem_pool_config.h ---------------------------------------------- Copyright (c) TCC 2013 Project : fish & Date : 2012/09/06 Files : Brief : 根据枚举值,和宏定义改变fish_mem_pool的配置 Update : Note : -----------------------------------------------------------------*/ #ifndef _FISHLIB_FISH_MEM_POOL_CONFIG_H_ #define _FISHLIB_FISH_MEM_POOL_CONFIG_H_ #define _MULTI_THREAD_ //定义多线程环境 //这里是采用了boost做多线程同步,可以自行更改 //但是必须符合lock和unlock的接口 //多线程宏定义 #ifdef _MULTI_THREAD_ #include <boost/thread/mutex.hpp> //#include <boost/signals2/mutex.hpp> typedef boost::mutex _LOCK_CORE; #endif //_MULTI_THREAD_ namespace fishlib{ enum _config_val{ _max_len = 8192, //内存池能分配的最大内存数,大于这个数的全都使用malloc _new_chunk_len = 4096000 //新chunk的长度 //因为只有256个区块,所以内存池最多能分配_new_chunk_len * 256的内存 //请衡量_max_len / _new_chunk_len的值,这个值越大则一个chunk的利用率可能会下降 //_max_len / _new_chunk_len越小,则chunk的利用率越高 //尽量保持在1/2以下 }; } #endif //_FISHLIB_FISH_MEM_POOL_CONFIG_H_
内存池的声明头文件:
fish_mem_pool.h
/* @fish_mem_pool.h ---------------------------------------------- Copyright (c) TCC 2013 Project : fish & Date : 2012/09/06 Files : Brief : Update : Note : -----------------------------------------------------------------*/ #ifndef _FISHLIB_FISH_MEM_POOL_H_ #define _FISHLIB_FISH_MEM_POOL_H_ namespace fishlib{ class fish_mem_pool{ public: /* *大于_max_len的直接用malloc分配,分配失败返回NULL */ static void *malloc(int len); /* *由fishlib::fish_mem_pool::malloc分配的都要用这个函数释放 */ static void free(void *p); /* *手动收缩,调用后内存池会把空闲块还给系统,返回释放了多少内存 */ static int shrink(); }; } #endif //_FISHLIB_FISH_MEM_POOL_H_
内存池实现文件:
fish_mem_pool.cpp
/* @fish_mem_pool.cpp ---------------------------------------------- Copyright (c) TCC 2013 Project : fish & Date : 2012/09/06 Files : Brief : Update : Note : -----------------------------------------------------------------*/ #include "fish_mem_pool.h" #ifndef _FISHLIB_FISH_MEM_POOL_CONFIG_H_ #include "fish_mem_pool_config.h" #endif //_FISHLIB_FISH_MEM_POOL_CONFIG_H_ #include <stdlib.h> #include <memory.h> #include <assert.h> //模板类,好处是如果没有用到则不会生成,甚至不会检测语法错误 template<typename T> class _MEM_POOL_LOCK{ public: //自动加锁 _MEM_POOL_LOCK(T &t_lock): _t_lock(t_lock){ _t_lock.lock(); } //自动在跳出作用域时解锁 ~_MEM_POOL_LOCK(){ _t_lock.unlock(); } private: T &_t_lock; }; //多线程宏定义 #ifdef _MULTI_THREAD_ static _LOCK_CORE _S_lc; #define FISH_LOCK _MEM_POOL_LOCK<_LOCK_CORE> mpl(_S_lc); #ifdef _DEBUG static _LOCK_CORE _S_dbg_lc; #define FISH_DBG_LOCK _MEM_POOL_LOCK<_LOCK_CORE> mpl(_S_dbg_lc); #endif //_DEBUG #else //在非多线程状态下,此宏定义不会做任何事情 #define FISH_LOCK #ifdef _DEBUG #define FISH_DBG_LOCK #endif //_DEBUG #endif //_MULTI_THREAD_ namespace fishlib{ enum default_val{ _max_chunk_cnt = 256 //最大区块数,默认值,只能是小于256的数字 }; //内存池全局变量 //记录内存池状态 struct _chunk; static _chunk *_S_chunk_list[_max_chunk_cnt]; //区块表,记录所有区块 static unsigned char _S_cur_valid_index = 0; //当前指向的有效区块,有效区块有且只有一个 static unsigned char _S_invalid_index_s = 0; //第一个无效的区块 static unsigned char _S_invalid_cnt = 0; //无效区块总数 static unsigned char _S_free_index_s = 0; //自由区开始索引 static int _S_total_chunk_cnt = 0; //总共的区块数 ////////////////////////////////////////////////////////////////////////// //内存池内部使用结构定义 #pragma pack(1) //区块,用于保存未分配内存(被分割出去) struct _chunk{ enum{ _chunk_struct_size = sizeof(int) + sizeof(int) + sizeof(int) }; int _left_mem; //此chunk剩余长度 int _alloc_org; //指向可用于分配内存快头的指针 int _need_to_rel;//需要释放多少内存 char _mem[1]; //此chunk持有的内存区 inline static _chunk *new_chunk(){ if (_S_total_chunk_cnt < _max_chunk_cnt){ //如果总区块数小于最大限制则可以分配 _chunk *p = (_chunk *)::malloc(_chunk_struct_size + _new_chunk_len); if (p == NULL) //系统内存不足 return NULL; //最终分配成功 ++_S_total_chunk_cnt; p->_alloc_org = 0; p->_left_mem = _new_chunk_len; p->_need_to_rel = _new_chunk_len; return p; }else{ return NULL; } } inline static void delete_chunk(_chunk *p){ //一个chunk销毁,并且把内存交还给系统 --_S_total_chunk_cnt; ::free(p); } }; //内存块,用于记录已分配内存 struct _mem_piece{ enum{ _mem_piece_struct_size = sizeof(char) + sizeof(int) }; int _mp_len; //内存块大小 char _chunk_index; //内存块所属区块的索引 char _mem[1]; //内存块的内存首地址 }; #pragma pack() ////////////////////////////////////////////////////////////////////////// //用于内存池自动初始化的结构 struct _init_mem_pool{ _init_mem_pool(){ //内存池初始化代码 memset(_S_chunk_list, 0, _max_chunk_cnt * sizeof(_chunk *)); //清零 _S_chunk_list[0] = _chunk::new_chunk(); _S_cur_valid_index = 0; _S_invalid_index_s = 0; _S_invalid_cnt = 0; } ~_init_mem_pool(){ } }; //自动静态对象,在此调用初始化代码 static _init_mem_pool imp; //助手函数 //从一个chunk中获取内存 static inline void *_get_mem(int need_len){ //取出当前有效chunk _chunk *p = _S_chunk_list[_S_cur_valid_index]; if (p->_left_mem <= need_len){ //内存不足 //因为在剩余空间恰巧等于的时候情况比较复杂,故舍弃这种可能 return NULL; }else{ _mem_piece *mp = (_mem_piece *)&p->_mem[p->_alloc_org]; //抽取内存 //剩余内存较少 //指向可用于分配内存快头的指针增加 p->_left_mem -= need_len; p->_alloc_org += need_len; //内存块真正的大小 mp->_mp_len = need_len; //chunk标识 mp->_chunk_index = _S_cur_valid_index; return mp->_mem; } } //获得自由区块总长 static inline int _get_free_chunk_cnt(){ return _S_total_chunk_cnt - (_S_invalid_cnt + 1); } //增加_S_cur_valid_index static inline unsigned char _inc_cur_idx(unsigned char idx){ ++idx; if (_S_chunk_list[idx] == NULL){ idx -= _S_total_chunk_cnt; } return idx; } //使释放无效区块,成为自由区块 static inline void _free_invalid_chunk(unsigned char index){ //把要成为自由chunk的区块变为初始状态 _S_chunk_list[index]->_alloc_org = 0; _S_chunk_list[index]->_left_mem = _new_chunk_len; _S_chunk_list[index]->_need_to_rel = _new_chunk_len; //与第一个无效区块交换,再++_S_invalid_index_s使其挪出无效区域 _chunk *ptmp = _S_chunk_list[index]; _S_chunk_list[index] = _S_chunk_list[_S_invalid_index_s]; _S_chunk_list[_S_invalid_index_s] = ptmp; _S_invalid_index_s = _inc_cur_idx(_S_invalid_index_s); --_S_invalid_cnt; } //真正移动块 static inline void _in_next_chunk(){ //需要释放的内存 - 剩余内存,意义:剩余的内存不需要释放 unsigned char index = _S_cur_valid_index; _chunk *p = _S_chunk_list[index]; int need_to_rel = p->_need_to_rel; need_to_rel -= p->_left_mem; p->_need_to_rel = need_to_rel; //移动到下一个 _S_cur_valid_index = _inc_cur_idx(_S_cur_valid_index); //扩充无效区域 ++_S_invalid_cnt; assert(p->_need_to_rel >= 0 && p->_need_to_rel <= _new_chunk_len); if (p->_need_to_rel == 0){ //如果内存块其他分配出去的都被用完了 //那就让它直接成为自由chunk _free_invalid_chunk(index); } } //尝试移动区块 //失败返回false static inline bool _next_chunk(){ if (_get_free_chunk_cnt() == 0){ //自由区耗尽,需要抽取内存 if (_S_total_chunk_cnt >= _max_chunk_cnt){ //无法抽取函数,池已满 return false; }else{ int idx = _S_cur_valid_index + 1; _chunk *p = _chunk::new_chunk(); if (p == NULL){ //系统内存已经彻底耗尽了 return false; }else{ //初始化新位置 _S_chunk_list[idx] = p; //移动到新位置 _in_next_chunk(); return true; } } }else{ //使用自由块 _in_next_chunk(); _S_free_index_s = _inc_cur_idx(_S_free_index_s); return true; } } //内存不足返回NULL void *fish_mem_pool::malloc(int ilen){ assert(ilen > 0); int real_need_len = ilen + _mem_piece::_mem_piece_struct_size; if (real_need_len > _max_len){ //要求的内存太大 //委托给malloc _mem_piece *p = (_mem_piece *)::malloc(real_need_len); if (p == NULL) return NULL; p->_chunk_index = 0; p->_mp_len = real_need_len; return p->_mem; }else{ //加锁 FISH_LOCK void *p = _get_mem(real_need_len); if (p == NULL){ //当前chunk的内存已经耗尽,尝试移动到下一个chunk bool succeed = _next_chunk(); if (succeed){ return _get_mem(real_need_len); }else{ return NULL; } }else{ return p; } } } void fish_mem_pool::free(void *p){ _mem_piece *pmem = (_mem_piece *)((char *)p - _mem_piece::_mem_piece_struct_size); if (pmem->_mp_len <= _max_len){ //加锁 FISH_LOCK unsigned char cindex = pmem->_chunk_index; _S_chunk_list[cindex]->_need_to_rel -= pmem->_mp_len; if (_S_chunk_list[cindex]->_need_to_rel == 0){ //发现需要释放的内存已经释放完,则让chunk变为自由chunk _free_invalid_chunk(cindex); } }else{ //内存块太大,是委托给malloc的内存块 ::free(pmem); } } int fish_mem_pool::shrink(){ //加锁 FISH_LOCK int free_cnt = _get_free_chunk_cnt(); int cnt = 0; unsigned char idx = 0; while (cnt != free_cnt){ if (_S_chunk_list[idx] != NULL){ if (_S_chunk_list[idx]->_left_mem == _new_chunk_len){ _chunk::delete_chunk(_S_chunk_list[idx]); _S_chunk_list[idx] = NULL; ++cnt; } } ++idx; } return (free_cnt * (_chunk::_chunk_struct_size + _new_chunk_len)); } }