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?

所以下一步,当然需要把那些简化掉的功能加上,如加锁、内存释放等等。

版权声明:本文为tkorays原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/tkorays/p/simple_memory_pool.html