内存池,Memory Pool,是一种高效的内存分配方式。它通过一次性向操作系统申请大块内存,用户直接从池中申请内存而不用释放,从而避免了不断申请、释放内存造成的内存碎片而降低软件性能。
一个比较完善的内存池至少需要具备一下功能:
在设计内存池时通常需要考虑一下几个问题:
对于一个通用的内存池来说,单例是比较合理的。
但是也有一些场景中,内存池并不是用于全局的,而是有各自的作用域和生命周期。如nginx中为每次连接开辟一个内存池;如我们可以为每个窗口创建一个内存池,窗口销毁后内存池也跟着释放了。
内存分配不可避免地需要对内存的管理指针进行操作,因此如果内存池用在多线程应用程序中就需要考虑,是否需通过加锁等进行同步。
为了效率,内存池在设计时通常会考虑机器字长对齐。
从内存池中申请的内存释放是一个比较复杂的问题,因为需要对内存进行重排,一般会用到AVL树
或B树
等。但是存在一些场景,内存池可以起到一个GC的作用。在这些场景中,需要频繁申请内存,且其生命周期很短,于是可以用一个简单内存池来管理内存,程序只用申请内存不用考虑释放问题。
本文的简单内存池即是模仿Nginx的内存池,去掉了一些回调处理、大内存申请,最后得到一个简化后的内存池。特点:
为了效率(其实是因为,用C更显Bigger高),这里采用C实现。
该内存池是由一些列block组成,每个block默认大小为MEM_POOL_BLOCK_DEFAULT_SIZE
,这些block以链表方式连接。内存池创建时,只会生成一个block,内存池不够时,自动扩充。
#define MEM_POOL_BLOCK_DEFAULT_SIZE 1024
typedef struct mem_block_s mem_block_t;
typedef struct mem_pool_s mem_pool_t;
struct mem_block_s {
char *last; /* 空闲内存start */
char *end; /* 该block最后地址 */
mem_block_t *next; /* 下一个block指针 */
};
struct mem_pool_s {
mem_block_t *head; /* 首个block */
mem_block_t *current; /* 当前可分配内存block */
};
内存池的接口如下:
mem_block_t *mem_block_create();
void mem_block_destroy(mem_block_t *blk);
size_t mem_pool_block_num(mem_pool_t *pool);
/* 用户接口 */
mem_pool_t *mem_pool_create();
void mem_pool_destroy(mem_pool_t *pool);
void *mem_pool_alloc(mem_pool_t *pool, size_t n); /* 申请的内存没有初始化为0 */
mem_block_t* mem_block_create(){
char* m;
mem_block_t* blk;
m = (char*)malloc(MEM_POOL_BLOCK_DEFAULT_SIZE + sizeof(mem_block_t));
if(!m){
return 0;
}
blk = (mem_block_t*)m;
blk->last = m + sizeof(mem_block_t);
blk->end = m + MEM_POOL_BLOCK_DEFAULT_SIZE + sizeof(mem_block_t);
blk->next = 0;
return blk;
}
void mem_block_destroy(mem_block_t* blk){
if(blk){
free(blk);
}
}
mem_pool_t* mem_pool_create(){
mem_pool_t* pool;
mem_block_t* blk;
pool = (mem_pool_t*)malloc(sizeof(mem_pool_t));
if(!pool){
return 0;
}
blk = mem_block_create();
if(!blk){
free(pool);
return 0;
}
pool->head = blk;
pool->current = blk;
return pool;
}
void mem_pool_destroy(mem_pool_t* pool){
mem_block_t* cur;
mem_block_t* next;
if(!pool){
return;
}
cur = pool->head;
while(cur){
next = cur->next;
mem_block_destroy(cur);
cur = next;
}
free(pool);
}
/* 为了简单,没有考虑到地址对齐 */
void* mem_pool_alloc(mem_pool_t* pool, size_t n){
char* m;
int is_size_valid;
int left_size;
mem_block_t* blk;
is_size_valid = ( n<=0 )||(n > MEM_POOL_BLOCK_DEFAULT_SIZE);
if(!pool || !pool->current || is_size_valid){
return 0;
}
left_size = pool->current->end - pool->current->last;
if(n > left_size){
blk = mem_block_create();
if(!blk){
return 0;
}
pool->current->next = blk;
pool->current = blk;
m = blk->last;
blk->last += n;
return m;
}
m = pool->current->last;
pool->current->last += n;
return m;
}
size_t mem_pool_block_num(mem_pool_t* pool){
mem_block_t* blk;
size_t cnt = 0;
if(!pool){
return 0;
}
blk = pool->head;
while(blk){
cnt++;
blk = blk->next;
}
return cnt;
}
这个实现真的相当简单,这里就不多费唇舌解释了。
上面实现了一个简单内存池,虽然有很多缺点但是却在很多地方可以直接用。但是,作为一个立志写出伟大程序的我们,怎么能止于此呢!!不用AVLTree、BTree秀一把,怎么对得起那些死去的bug?
所以下一步,当然需要把那些简化掉的功能加上,如加锁、内存释放、地址对齐等等。
如果您觉得文章对您有用能够解决您的问题,欢迎您通过扫码进行打赏支持,谢谢!