概述:本文介绍用户层内存池的实现
Q:为什么需要内存池?
A:在项目中,用户层通过malloc系统调用申请内存的次数可能很多,每一次malloc系统调用,都会引起用户态——内核态的切换,这样的开销对性能的影响是不容忽视的,尤其是当你为了申请一段很小的空间而调用malloc时,性能的损失达到了最大化
Q:什么是内存池?
解决方案:只调用一次malloc
A:只调用一次malloc申请足够大的内存空间,后续需要使用内存时,从已申请的内存中取出一块,这就是内存池的概念
这样就避免了频繁进行malloc系统调用,大大降低了性能损失
内存池的实现
思路:
1.内存池的空间由一次malloc调用分配
2.此时的内存池是一整块连续的内存空间
3.使用时,每次应该分配出一小块(固定大小或任意大小)
4.用户调用malloc申请内存,应该得到一块空间的起始地址
5.用户free归还一块内存时,这块内存需要能被重复使用
为了简要说明原理,本文介绍分配固定大小的内存池版本
后面使用到的二级指针,不懂的朋友可以先移步博主的 数据结构专栏——二级指针 这篇文章
1.内存池结构体定义:
typedef struct mempool_s { int block_size; // 内存池最小单元大小 int free_count; // 空余block数量 char *free_ptr; // 当前空余的块的地址 char *mem; // 整个内存池的首地址 } mempool_t;
2.初始化,为内存池申请空间:
int mp_init(mempool_t *m, int size) { // 初始化内存池,指定大小 if (!m) return -1; if (size < 16) size = 16; // 最小内存池尺寸为 16字节 m->block_size = size; m->mem = (char *)malloc(MEM_PAGE_SIZE); // 为内存池分配空间 if (!m->mem) return -1; m->free_ptr = m->mem; // 当前空闲的区域指针 m->free_count = MEM_PAGE_SIZE / size; // 二级指针 // 将一整块内存抽象为多块:块大小为size,每一块的首地址的前8个字节保存下一块的首地址 int i = 0; char *ptr = m->free_ptr; for (i = 0;i < m->free_count; i++) { *(char **)ptr = ptr + size; // 将当前块的首地址的前8个字节保存下一块的首地址 ptr += size; } *(char **)ptr = NULL; // 最后一块 return 0; }
3.用户层接口:malloc
void *mp_alloc(mempool_t *m) { // 向内存池申请一块内存 if (!m || m->free_count == 0) return NULL; // 如果内存池不存在或没有剩余空间 void *ptr = m->free_ptr; m->free_ptr = *(char **)ptr; // 更新free指针,指向下一个空余的内存块 m->free_count --; return ptr; }
4.用户层接口:free
void mp_free(mempool_t *m, void *ptr) { *(char **)ptr = m->free_ptr; // 将即将被回收的块,指向当前空余的块 m->free_ptr = (char *)ptr; // 将当前空余的块修改为即将回收的块 // 相当于把回收的块插入空余队列的第一个位置,优先分配回收的块 m->free_count ++; }
5.测试函数main
int main() { // 测试 mempool_t m; mp_init(&m, 32); void *p1 = mp_alloc(&m); printf("1: mp_alloc: %p\n", p1); void *p2 = mp_alloc(&m); printf("2: mp_alloc: %p\n", p2); void *p3 = mp_alloc(&m); printf("3: mp_alloc: %p\n", p3); void *p4 = mp_alloc(&m); printf("4: mp_alloc: %p\n", p4); mp_free(&m, p2); void *p5 = mp_alloc(&m); printf("5: mp_alloc: %p\n", p5); return 0; }