一个简单的内存池(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放另外一个链表,再做一些分配上的改动,也许能提高一些速度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值