一个简单的内存池(c实现)之一

一个简单的内存池(c实现)之一

都知道频繁分配内存释放内存很耗系统资源,而且容易造成内存碎片。因此写了个简单的内存池实现,越简单越好,为什么?做复杂了效率还不如直接malloc。因此这个内存池采用链表连接内存块的方式,分配的是固定大小的内存块,从池中取内存和归还内存是用的空闲链表堆栈操作, 没有使用线程锁,如果要线程安全,建议在外部调用内存池的地方加锁。

   做过一个简单的测试,10万次内存池调用的效率大概比直接分配释放内存提高了30-50%。但是前提是内存池不能加锁(pthread_mutex),加锁的内存池效率和直接分配内存的效率差不多,有时候还要多点点。(测试的环境是每次2K,4个双核CPU,FREEBSD7)

代码实现:
struct memblock
{
   
int               used;
   
void*             data;
   
struct memblock* next;
   
struct memblock* createnext;
}
;


struct mempool
{
    
int              size; // memblock大小
     int              unused; // 空闲的memblock大小
     int              datasize; // 每次分配的数据大小(就是memblock.data)
     struct memblock *      free_linkhead; // 空闲memblock链表头
     struct memblock *      create_linkhead; // 所有创建的memblock链表头,内存池释放的时候使用,防止内存池释放的似乎还有memblock未归还的情况
    
};
typedef
void ( * free_callback)( void * ); // 释放回调函数,释放membloc.data用,可以简单的直接用free函数

void      mempool_init( int initialSize, int datasize); // 初始化mempool
void      mempool_dealloc( struct mempool * pool,free_callback callback); // 释放mempool
void *      mempool_get( struct mempool * pool); // 获取一个memblock
void      mempool_release( struct mempool * pool, struct memblock * block); // 归还一个memblock

/* ********************************
* mempool
* *****************************
*/
// malloc一个memblock
static struct memblock * mempool_allocblock( struct mempool * pool );

// ------------------implement--------
void *
mempool_init(
int initialSize, int datasize )
{
    
struct mempool * pool = malloc( sizeof ( struct mempool ) );
     pool
-> unused = 0 ;
     pool
-> datasize = datasize;
     pool
-> free_linkhead = NULL;
  
    
// 预先初始化initialSize个内存块
      pool -> create_linkhead = NULL;
    
int i;
    
for ( i = 0 ; i < initialSize; i ++ ) {
        
struct memblock * block = mempool_allocblock( pool );
         mempool_release( pool, block );
     }
    
return ( pool );
}

void
mempool_dealloc(
struct mempool * pool, free_callback callback )
{
    
struct memblock * block = NULL;
    
// 将所有创建的memblock释放了
     while ( pool -> create_linkhead != NULL ) {
         block
= pool -> create_linkhead;
         pool
-> create_linkhead = pool -> create_linkhead -> createnext;
    
// 执行free回调。
         if ( callback ) {
             (
* callback )( block -> data );
         }
         free( block );
     }
     free( pool );
     L_DEBUG(
" %s:size(%d),unused(%d) " , __func__, pool -> size, pool -> unused );
}

static struct memblock *
mempool_allocblock(
struct mempool * pool )
{
    
struct memblock * block = malloc( sizeof ( struct memblock ) );
     block
-> data = malloc( sizeof ( pool -> datasize ) );
     block
-> next = NULL;
     block
-> used = 1 ; // 表示已使用

    
// 加入所有创建的memblock的链表头
     block -> createnext = pool -> create_linkhead;
     pool
-> create_linkhead = block;

     pool
-> size ++ ;
    
return ( block );
}

void
mempool_release(
struct mempool * pool, struct memblock * block )
{
    
if ( block == NULL ) {
         L_WARN(
" %s:release a NULL! " , __func__ );
        
return ;
     }
    
if ( block -> used != 1 ) {
         L_WARN(
" %s:used!=1 " , __func__ );
        
return ;
     }
    
// 将归还的内存块放到空闲链表头。
     block -> used = 0 ; // 表示空闲
     block -> next = pool -> free_linkhead;
     pool
-> free_linkhead = block;
     pool
-> unused ++ ; // 空闲数+1
}

void *
mempool_get(
struct mempool * pool )
{
   
    
struct memblock * block = NULL;
    
if ( pool -> free_linkhead ) {
    
// 从空闲链表头取出一个内存块
         block = pool -> free_linkhead;
         pool
-> free_linkhead = pool -> free_linkhead -> next;
         block
-> next = NULL;
         block
-> used = 1 ; // 表示已使用
         pool -> unused -- ; // 空闲内存块数-1
     }
    
else {
    
// 没有空闲的内存块,创建一个
         block = mempool_allocblock( pool );
     }
    
return ( block );
}
上面这个内存池的实现其实更像一个后备列表的实现。使用上来说不是很方便,要申请的内存块是一个BLOCK结构的一个个成员,而且每次从系统内存堆中申请都是一小块一小块,也没有考虑字节对齐。因此让我们来看看新的一个内存池的实现吧。    这个内存池是根据《c++应用程序性能优化》书里的固定尺寸的内存池原理做了一些改动用C语言写的。大家有兴趣可以去看看,里面说的最详细。    简单说下这个内存池的原理,内存池里由N个memblock以一个双向链表组成,每个memblock的组成是一个HEAD块+M个固定长度的memchunk组成,memchunk就是你将来要从池中申请的内存块。    我们来看下如下几个情况:    1.内存池初始化后,内存池的memblock链表头是NULL。    2.第一次从池中申请一个memchunk,内存池根据initsize和chunksize从系统内存堆中申请一个(memblock head)+ chunksize*initsize的内存块,对block head部分数据字段进行初始化,并将每个chunk的头4个字节来存放该memblock里下个可用chunk的编号,因为是固定长度的chunk,所以,可以很容易根据编号和chunk长度计算出chunk的地址。创建了memblock后,将第一个chunk (设为A) 返回给用户,并将block head的first字段设置为chunk A头4个字节的值(也就是下个可用chunk编号)。同时将创建的block加入到链表头中。    3.下次申请memchunk的时候,遍历链表,找出有空闲chunk的BLOCK,对BLOCK进行和第一次申请时类似的处理。同时检查该BLOCK里还有多余的空闲chunk不,有的话就将该block移动到链表头部。以提高下次申请时遍历链表的速度。如果遍历完链表也没有找到有空闲chunk的block,就从系统内存堆中申请一个BLOCK,将之加入到链表头。     4.将申请的memchunk (假设为A)归还给池的时候,遍历memblock链表,根据A的地址来找出A所在的block。      找到后根据这个 memchunk A 的地址计算出它的编号;      将block->first 的编号存入A的头4个字节中; 将block->first更改为A的编号。(就是chunk的链表操作)      最后,将A所在的这个memblock移动到链表头(因为有空闲chunk),以提高申请chunk时的速度。(链表只需遍历一次)。在书中,这里还有个处理:如果该block的chunk都是空闲的,就把block释放了(归还给系统内存堆),我没有这样做,打算单独写个清理的操作。             大概原理就是这样,考虑到和64位机兼容,chunk和block都按8字节对齐。代码中的memheap就是mempool。只是名称我该成heap了。。      在后面的代码中,对内存池实现有比较详细的注释。回顾下这个内存池的原理,明显的优点是减少了内存碎片,字节对齐,但是有个显而易见的问题是,如果内存池中有大量(成千上万)个memblock的话,对block的遍历检索将是一个性能瓶颈,申请chunk的操作还好点,内部做了一些优化处理,归还chunk时查找链表的速度将比较慢,最坏的情况是有多少个memblock就检索多少次。。可以考虑对这里做一些检索上的优化和更改,不用双向链表,用其他方式来做。最简单的优化就是用游戏粒子系统里普遍使用的一种算法,将有空闲chunk的block放一个链表,没有空闲chunk的block放另外一个链表,再做一些分配上的改动,也许能提高一些速度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值