[原创]网络专用高效内存池,支持多线程.原创,非sgi的内存池

原帖地址:http://www.cnblogs.com/yhblog/archive/2013/05/27/3101125.html

  首先要说明一点,这个内存池使用时需要注意的,如果想用在长期分配出去的内存,请慎用.
  因为假如一个区块被分配完了,只有在这个区块里已分配的内存被完全释放后,这个区块才能重用.


因为当初是设计为网络分包用的内存池.为了效率而采用这个策略的.


  发代码之前先简单介绍下内存池的思路.
  内存池分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]; //内存块的内存首地址
};


 



  内存池的所有区块可以分为三个区域


  无效区:这里的区块已经完全分配完毕,等待释放,释放完成后会到自由区


  待分配区:这个区有且只有一个块,分配内存都会从这里抽取


  自由区:


  下图是内存池的状态:
  [原创]网络专用高效内存池,支持多线程.原创,非sgi的内存池_第1张图片
内存池一开始会处于以下的状态
[原创]网络专用高效内存池,支持多线程.原创,非sgi的内存池_第2张图片
初始化的时候第一个区块会被初始化.继续分配就会耗尽绿色的待分配块,内存池会把它归入无效区,同时尝试从系统创建一个新的区块并列为待分配块
只有无效区的内存的块被完全释放后才会转到自由区.当然,如果自由区有区块的话,就不需要新建区块,而是直接指定自由区的第一个区块为待分配区块

  然后我再来说明一下分配的策略._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
= 4096, //内存池能分配的最大内存数,大于这个数的全都使用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 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 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;
--_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;
//扩充无效区域
++_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(){
unsigned
char new_index = _S_cur_valid_index + 1;
if (new_index == _S_invalid_index_s){
//发现继续向前移动到达无效区域,则说明内存池已经彻底分配完了
return false;
}
else{
//内存池尚有剩余位置
if (_S_chunk_list[new_index] == NULL){
//新位置尚未初始化,则向系统索取内存
_chunk *p = _chunk::new_chunk();
if (p == NULL){
//系统内存已经彻底耗尽了
return false;
}
else{
//初始化新位置
_S_chunk_list[new_index] = p;
//移动到新位置
_in_next_chunk();
return true;
}
}
else{
//直接移动到新位置
_in_next_chunk();
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

unsigned
char cur_index = _S_cur_valid_index + 1;
unsigned
char free_cnt = _max_chunk_cnt - (_S_invalid_cnt + 1);

int index = 0;
int end = free_cnt;
int cnt = 0;

//释放所有自由区块
for (; index < end; ++cur_index, ++index){
if (_S_chunk_list[cur_index] != NULL){
++cnt;

//必须为自由chunk
assert(_S_chunk_list[cur_index]->_need_to_rel == _new_chunk_len);

_chunk::delete_chunk(_S_chunk_list[cur_index]);
_S_chunk_list[cur_index]
= NULL;
}
}
return (cnt * (_chunk::_chunk_struct_size + _new_chunk_len));
}

}


 


 


  


  
  

本文链接

你可能感兴趣的:(资料收集)