内存池的实现

简介: 内存池的实现

概述:本文介绍用户层内存池的实现

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;
}

推荐学习 https://xxetb.xetslk.com/s/p5Ibb

目录
相关文章
|
2月前
3.1.2 内存池的实现与场景分析
3.1.2 内存池的实现与场景分析
|
2月前
|
Kubernetes 网络性能优化 调度
k8s的内存分配
k8s的内存分配
|
2月前
|
应用服务中间件 nginx
|
2月前
|
应用服务中间件 nginx
内存池的实现与分析
内存池的实现与分析
34 0
|
2月前
|
存储
内存池的实现与场景分析
内存池的实现与场景分析
44 0
|
2月前
|
存储 C#
C# | 内存池
内存池可以复用已申请的内存空间,它在程序启动时预先分配一定数量的内存块,当你需要使用内存时,则会从内存池中分配一块空闲内存,也就是说并不是每次都会向系统申请一块新的内存空间。 同理,当你使用完一块内存空间后,并不是直接释放内存,而是将其归还到内存池中。内存池就是通过这样一借一还的方式避免了频繁地分配和释放内存,减少了内存碎片和系统开销,提高了程序的性能。
45 0
|
存储 安全 程序员
内存分配理解
内存分配理解
124 0
|
存储 C语言
内存分配与编译处理
内存分配与编译处理
101 0
内存分配与编译处理
|
缓存 C语言
内存分配
内存分配
176 0
|
Java
v8内存分配浅谈
### 前言 本文会通过V8中对String对象的内存分配开始分析,对中间出现的源码进行解读。对heap内存的新生代分配和老生代内存分配的过程解读。首先,我们来看一张流程图,该流程图给出整个分配过程中的前期流程图,其中省略了一些步骤,只给出了关键的步骤。 ![image1](http://blog.magicare.me/content/images/2018/09/v8_string
1502 0