tkorays(tkorays@hotmail.com)
内存池,Memory Pool,是一种高效的内存分配方式。它通过一次性向操作系统申请大块内存,用户直接从池中申请内存而不用释放,从而避免了不断申请、释放内存造成的内存碎片而降低软件性能。
1. 内存池功能
一个比较完善的内存池至少需要具备一下功能:
- 内存池的创建与销毁
- 当内存池容量不够时,自身自动扩容
- 向内存池申请内存(通常会考虑字节对齐)
- 『释放』内存,将使用的内存池归还给内存池
- 整理内存块,将不用的大内存块归还给操作系统
在设计内存池时通常需要考虑一下几个问题:
1.1 内存池需要设计成单例的吗?
对于一个通用的内存池来说,单例是比较合理的。
但是也有一些场景中,内存池并不是用于全局的,而是有各自的作用域和生命周期。如nginx中为每次连接开辟一个内存池;如我们可以为每个窗口创建一个内存池,窗口销毁后内存池也跟着释放了。
1.2 内存池是否要加锁?
内存分配不可避免地需要对内存的管理指针进行操作,因此如果内存池用在多线程应用程序中就需要考虑,是否需通过加锁等进行同步。
1.3 内存池的字节对齐
为了效率,内存池在设计时通常会考虑机器字长对齐。
1.4 申请的内存是否需要归还给内存池?
从内存池中申请的内存释放是一个比较复杂的问题,因为需要对内存进行重排,一般会用到AVL树
或B树
等。但是存在一些场景,内存池可以起到一个GC的作用。在这些场景中,需要频繁申请内存,且其生命周期很短,于是可以用一个简单内存池来管理内存,程序只用申请内存不用考虑释放问题。
2. 简单内存池实现
本文的简单内存池即是模仿Nginx的内存池,去掉了一些回调处理、大内存申请,最后得到一个简化后的内存池。特点:
- 适用于频繁申请小内存
- 申请的内存不用释放(意味着不能适用于申请很多内存情况)
- 销毁内存池时释放所有内存
为了效率(其实是因为,用C更显Bigger高),这里采用C实现。
该内存池是由一些列block组成,每个block默认大小为MEM_POOL_BLOCK_DEFAULT_SIZE
,这些block以链表方式连接。内存池创建时,只会生成一个block,内存池不够时,自动扩充。
1 #define MEM_POOL_BLOCK_DEFAULT_SIZE 1024 2 3 typedef struct mem_block_s mem_block_t; 4 typedef struct mem_pool_s mem_pool_t; 5 6 struct mem_block_s { 7 char *last; /* 空闲内存start */ 8 char *end; /* 该block最后地址 */ 9 mem_block_t *next; /* 下一个block指针 */ 10 }; 11 12 struct mem_pool_s { 13 mem_block_t *head; /* 首个block */ 14 mem_block_t *current; /* 当前可分配内存block */ 15 };
内存池的具体实现如下:
1 mem_block_t *mem_block_create(); 2 void mem_block_destroy(mem_block_t *blk); 3 size_t mem_pool_block_num(mem_pool_t *pool); 4 5 /* 用户接口 */ 6 mem_pool_t *mem_pool_create(); 7 void mem_pool_destroy(mem_pool_t *pool); 8 void *mem_pool_alloc(mem_pool_t *pool, size_t n); /* 申请的内存没有初始化为0 */
1 mem_block_t* mem_block_create(){ 2 char* m; 3 mem_block_t* blk; 4 5 m = (char*)malloc(MEM_POOL_BLOCK_DEFAULT_SIZE + sizeof(mem_block_t)); 6 if(!m){ 7 return 0; 8 } 9 10 blk = (mem_block_t*)m; 11 blk->last = m + sizeof(mem_block_t); 12 blk->end = m + MEM_POOL_BLOCK_DEFAULT_SIZE + sizeof(mem_block_t); 13 blk->next = 0; 14 return blk; 15 } 16 17 void mem_block_destroy(mem_block_t* blk){ 18 if(blk){ 19 free(blk); 20 } 21 } 22 23 mem_pool_t* mem_pool_create(){ 24 mem_pool_t* pool; 25 mem_block_t* blk; 26 27 pool = (mem_pool_t*)malloc(sizeof(mem_pool_t)); 28 if(!pool){ 29 return 0; 30 } 31 32 blk = mem_block_create(); 33 if(!blk){ 34 free(pool); 35 return 0; 36 } 37 38 pool->head = blk; 39 pool->current = blk; 40 41 return pool; 42 } 43 44 void mem_pool_destroy(mem_pool_t* pool){ 45 mem_block_t* cur; 46 mem_block_t* next; 47 48 if(!pool){ 49 return; 50 } 51 52 cur = pool->head; 53 while(cur){ 54 next = cur->next; 55 mem_block_destroy(cur); 56 cur = next; 57 } 58 59 free(pool); 60 } 61 62 void* mem_pool_alloc(mem_pool_t* pool, size_t n){ 63 char* m; 64 int is_size_valid; 65 int left_size; 66 mem_block_t* blk; 67 uintptr_t addr; 68 69 is_size_valid = ( n<=0 )||(n > MEM_POOL_BLOCK_DEFAULT_SIZE); 70 if(!pool || !pool->current || is_size_valid){ 71 return 0; 72 } 73 74 addr = ADDR_ALIGN(pool->current->last,ALIGN_SIZE); 75 left_size = pool->current->end - addr; /* may <= 0 */ 76 if(n > left_size){ 77 blk = mem_block_create(); 78 if(!blk){ 79 return 0; 80 } 81 82 pool->current->next = blk; 83 pool->current = blk; 84 m = blk->last; 85 blk->last += n; 86 return m; 87 } 88 89 // use current left room 90 m = pool->current->last; 91 pool->current->last += n; 92 return m; 93 } 94 95 96 size_t mem_pool_block_num(mem_pool_t* pool){ 97 mem_block_t* blk; 98 size_t cnt = 0; 99 if(!pool){ 100 return 0; 101 } 102 blk = pool->head; 103 while(blk){ 104 cnt++; 105 blk = blk->next; 106 } 107 return cnt; 108 }
这个实现真的相当简单,这里就不多费唇舌解释了。
3. 下一步:改进它
上面实现了一个简单内存池,虽然有很多缺点但是却在很多地方可以直接用。但是,作为一个立志写出伟大程序的我们,怎么能止于此呢!!不用AVLTree、BTree秀一把,怎么对得起那些死去的bug?
所以下一步,当然需要把那些简化掉的功能加上,如加锁、内存释放等等。