浅谈内存池几种设计方式

写服务器的,通常会涉及到内存池的东西,自己在这方面也看了写了一些东西,有些体会,写出来跟大家分享下。
      内存池基本包含以下几个东西,第一,初始化。第二,分配内存。第三,回收内存。所谓初始化,就是在服务器启动的时候,或者第一次需要内存的时候,系统分配很大的一块内存,方便之后的使用。分配内存,就是从内存池中取出需要的内存给外部使用,当然这里需要考虑的是当内存池中没有内存可分配时候的处理。回收内存,简单来说,就是外面对象生命期结束了,将分配出去的内存回收入内存池中。好了简单概念就说完了,我们先来看一种最简单的设计方式。
//为了方便描述,这里附上几个简单的链表操作宏
#define INSERT_TO_LIST( head, item, prev, next ) \
do{ \
if ( head ) \
(head)->prev = (item); \
(item)->next = (head); \
(head) = (item);          \
}while(0)
#define REMOVE_FROM_LIST(head, item, prev, next) \
do{ \
if ( (head) == (item) ) \
{ \
(head) = (item)->next; \
if ( head ) \
(head)->prev = NULL; \
} \
else \
{ \
if ( (item)->prev ) \
(item)->prev->next = (item)->next;          \
\
if ( (item)->next ) \
(item)->next->prev = (item)->prev;          \
} \
}while(0)
struct  student
{
      char name[32];
      byte sex;
      
      struct student *prev,*next;
};

static struct mem_pool
{
      //该指针用来记录空闲节点
       
  struct  student *free;
      //该变量记录分配结点个数
      size_t alloc_cnt;
}s_mem_pool;

//分配内存“块”的函数
bool mem_pool_resize(size_t size)
{
      //该函数创建size个不连续的对象,把他们通过链表的方式加入到s_mem_pool.free中
      for ( size_t i = 0;i < size;++i )
      {
             
  struct student *p = (struct student *)malloc(sizeof(struct student));
             
  if ( !p )
               return false;
            
            p->prev = p->next = NULL;
            INSERT_TO_LIST(s_mem_pool.free,p,prev,next);

      }

      s_mem_pool.alloc_cnt += size;
}

#define MEM_INIT_SIZE 512  
#define MEM_INC_SIZE 256
//初始化函数
bool mem_pool_init()
{
if ( !mem_pool_resize(MEM_INIT_SIZE) )
            return false;
return true;
}
struct student *get_data()
{
if ( s_mem_pool.free == NULL )
{
            if ( !mem_pool_resize(MEM_INC_SIZE) )
                  return NULL;
}
struct student *ret = s_mem_pool.free;
REMOVE_FROM_LIST(s_mem_pool.free,ret,prev,next)
return ret;
}
void free_data(struct student *p)
{
if ( !p )
            return;
memset(p,0,sizeof(struct student));
INSERT_TO_LIST(s_mem_pool.free,p,prev,next)
}

好了最简单的内存池的大致框架就是这样。我们先来看下他的过程。首先,在mem_pool_init()函数中,他先分配512个不连续的student对象。每分配出来一个就把它加入到free链表中,初始化完成后内存池大概是这样的


接下来就是从内存池中取出一个对象get_data()。函数先去判断是否有空闲的对象,有则直接分配,否则再向系统获取一"块"大的内存。调用一次后的内存池大概是这样的


释放对象,再把对象加入到Free链表中。
以上就是过程的简单分析,下面我们来看看他的缺点。
第一,内存不是连续的,容易产生碎片
第二,一个类型就得写一个这样的内存池,很麻烦
第三,为了构建这个内存池,每个没对象必须加上一个prev,next指针
好了,我们来优化一下它。我们重新定义下我们的结构体

union student
{
    
int  index;
    
struct
    {
        
char  name[ 32 ];
        
byte  sex;
    }s;
};

static   struct  mem_pool
{
    
// 该下标用来记录空闲节点
     int  free;
    
// 内存池
    union student  * mem;
    
// 已分配结点个数
    size_t alloc_cnt;
}s_mem_pool;

// 分配内存块的函数
bool  mem_pool_resize(size_t size)
{
    size_t new_size 
=  s_mem_pool.alloc_cnt + size;
    union student 
* tmp  =  (union student  * )realloc(s_mem_pool.mem,new_size * sizeof (union student));
    
if  (  ! tmp )
        
return   false ;
        
    memset(tmp
+ s_mem_pool.alloc_cnt, 0 ,size * sizeof (union student));
    size_t i 
=  s_mem_pool.alloc_cnt;
    
for  ( ;i  <  new_size  -   1 ; ++ i )
    {
        tmp[i].index 
=  i  +   1 ;
    }
    
    tmp[i].index 
=   - 1 ;
    s_mem_pool.free 
=  s_mem_pool.alloc_cnt;
    s_mem_pool.mem 
=  tmp;
    s_mem_pool.alloc_cnt 
=  new_size;
    
    
return   true ;
}

#define  MEM_INIT_SIZE    512  
#define  MEM_INC_SIZE    256
// 初始化函数
bool  mem_pool_init()
{
    
if  (  ! mem_pool_resize(MEM_INIT_SIZE) )
        
return   false ;
        
    
return   true ;
}

union student 
* get_data()
{
    
if  ( s_mem_pool.free  ==   - 1  )
    {
        
if  (  ! mem_pool_resize(MEM_INC_SIZE) )
            
return  NULL;
    }
    
    union student 
* ret  =  s_mem_pool.mem + s_mem_pool.free;
    s_mem_pool.free 
=  ret -> index;
    
return  ret;
}

void  free_data(union student  * p)
{
    
if  (  ! p )
        
return ;
    
    p
-> index  =  s_mem_pool.free;
    s_mem_pool.free 
=  p  -  s_mem_pool.mem;
}
我们来看看改进了些什么。第一student改成了联合体,这主要是为了不占用额外的内存,也就是我们上面所说的第三个缺点,第二,我们使用了realloc函数,这样我们可以使我们分配出来的内存是连续的。我们初始化的时候多了一个for循环,这是为了记录空闲对象的下标,当我们取出一个对象时,free可以立刻知道下一个空闲对象的位置,释放的时候,对象先记录free此时的值,接着再把free赋值成该对象在数组的下标,这样就完成了回收工作。
我们继续分析这段代码,问题在realloc函数上,如果我们的s_mem_pool.mem已经很大了,在realloc的时候我们都知道,先要把原来的数据做一次拷贝,所以如果数据量很大的情况下做一次拷贝,是会消耗性能的。那这里有没有好的办法呢,我们进一步优化
思路大概是这样
初始化


再次分配的时候,我们只需要重新分配新的内存单元,而不需要拷贝之前的内存单元。


因此基于此思路,我们修改我们的代码

#include  < stdio.h >
#include 
< stdlib.h >

struct  student
{
    
int  index;

    
char  name[ 32 ];
    
byte  sex;
};

static   struct  mem_pool
{
    
// 该下标用来记录空闲节点
     int  free;
    
// 内存池
     struct  student  ** mem;
    
// 已分配块个数
    size_t block_cnt;
}s_mem_pool;

#define  BLOCK_SIZE        256         // 每块的大小
// 分配内存块的函数
bool  mem_pool_resize(size_t block_size)
{
    size_t new_cnt 
=  s_mem_pool.block_cnt  +  block_size;
    
struct  student  ** tmp  =  ( struct  student  ** )realloc(s_mem_pool.mem,new_size * sizeof ( struct  student  * ));
    
if  (  ! tmp )
        
return   false ;
        
    memset(tmp
+ s_mem_pool.block_cnt, 0 ,size * sizeof ( struct  student * ));
    
for  ( size_t i  =  s_mem_pool.block_cnt;i  <  new_cnt; ++ i )
    {
        tmp[i] 
=  ( struct  student  * )calloc(BLOCK_SIZE, sizeof ( struct  student));
        
if  (  ! tmp[i] )
            
return   false ;
            
        size_t j 
=   0 ;
        
for (;j  <  BLOCK_SIZE  -   1 ; ++ j )
        {
            tmp[i][j].index 
=  i * BLOCK_SIZE + j + 1 ;
        }
        
        
if  ( i  !=  new_cnt - 1  )
            tmp[i][j].index 
=  (i + 1 ) * BLOCK_SIZE;
        
else
            tmp[i][j].index 
=   - 1 ;
    }
    
    s_mem_pool.free 
=  s_mem_pool.alloc_cnt * BLOCK_SIZE;
    s_mem_pool.mem 
=  tmp;
    s_mem_pool.block_cnt 
=  new_cnt;
    
    
return   true ;
}
 
#define  MEM_INC_SIZE    10
// 初始化函数
bool  mem_pool_init()
{
    
if  (  ! mem_pool_resize(MEM_INIT_SIZE) )
        
return   false ;
        
    
return   true ;
}

struct  student  * get_data()
{
    
if  ( s_mem_pool.free  ==   - 1  )
    {
        
if  (  ! mem_pool_resize(MEM_INC_SIZE) )
            
return  NULL;
    }
    
    
struct  student  * ret  =  s_mem_pool.mem[s_mem_pool.free / BLOCK_SIZE] + s_mem_pool.free % BLOCK_SIZE;
    
int  pos  =  s_mem_pool.free;
    s_mem_pool.free 
=  ret -> index;
    ret
-> index  =  pos;
    
return  ret;
}

void  free_data( struct  student  * p)
{
    
if  (  ! p )
        
return ;
    
    
int  pos  =  p -> index;
    p
-> index  =  s_mem_pool.free;
    s_mem_pool.free 
=  pos;
}
这里不一样的地方主要在 mem_pool_resize函数中,mem变成了2级指针,每次realloc的时候只需要分配指针数组的大小,无须拷贝对象,这样可以提高效率,但是为了在释放的时候把对象放回该放的位置,我们这里在结构体里加入了index变量,记录它的下标。在内存池里,它表示下个空闲对象的下标,在内存池外,它表示在内存池中的下标。总的来说满足了一个需求,却又带来了新的问题,有没有更好的方法呢,答案是肯定,不过今天先写到这里,明天继续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值