一个简单的内存池(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 上面这个内存池的实现其实更像一个后备列表的实现。使用上来说不是很方便,要申请的内存块是一个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放另外一个链表,再做一些分配上的改动,也许能提高一些速度。
{ 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 ); } |