内存池设计基本思路

466 阅读8分钟

内存池设计基本思路

设计内存池,以减少因频繁调用malloc造成中造成的内存碎片

基本结构

  • 选取key提升malloc效率: 对于一块堆内存,申请时指定大小,而释放时指定地址,因此,采用内存大小做key,malloc时更块,采用地址做key,free时更快。但在实际项目中,malloc的效率比free的效率更重要,因此采用大小做key。
20221223193052
  • 采用红黑树提升free效率: 由于释放内存时不知道该内存块的大小,遍历起来很慢,所以将每个key对应的链表组织成红黑树;free时不释放到内存,设置为空标记即可

应用问题

  • 线程安全问题:加锁
  • 内存回收策略:
    • 大块拆小块:128k的可以全部拆成16k的,也可以拆成1个64k,1个32k,2个16k
    • 小块合大块:由于需要小块相邻,可以直接把小块全部释放,然后重新向操作系统申请大块

现有内存表

现在一般都是用一个链表组织内存块,如STL内存池:

20221225204508

缺点:

  • 查找慢
  • 小块回收麻烦,都不是挨着的
  • 内存表的所有节点都是碎片,它们存在哪里,要怎么释放

使用场景:

  • 全局内存池:整个程序就用一个内存池 (推荐jemalloc、tcmalloc)
  • 一个长连接做一个内存池 (不用回收小块,反正时间不长)
  • 消息或者短连接不用做内存池,浪费

内存池设计

这里只针对一个长连接做一个内存池,小内存块不立即释放的,先上图:

20221227211441

解决的问题

  • 查找慢:
    • malloc时,直接在小块的last处分配即可;
    • free时,遍历每个块,如果这个指针p的地址大于小块的起始地址,小于小块的end指针,说明这个要释放的地址就在这个小块,不用释放这个p指向的内存,将这个小块的引用计数减一即可,当引用计数为0时,直接释放这个小块。
  • 小块回收麻烦:
    • malloc的小块内存都是挨着的,释放方便
  • 内存表节点碎片:
    • 节点都存在小块里,释放小块时,这个节点所占的内存也会跟着释放

结构讲解

  1. 内存池结构体
    • 放在第一个小块中(从第二个小块开始,都没这个结构体了)
    • large指针:指向大块链表
    • small指针:指向小块链表
    • current指针:指向当前分配内存的小块
  2. 小块结构体:
  • 以一页的大小作为小块
  • last指针:指向当前小块未分配内存的起始地址
  • end指针:指向当前小块内存块的结束地址
  • next指针:指向下一个小块
  • quote引用计数:在这个小块malloc成功一次加一,free一次减一,为0时代表这个小块可释放
  • failed计数:这个内存块malloc失败一次加一,达到指定次数(如4)时,不再在这个小块分配内存
  1. 大块结构体:
  • next指针:下一个大块
  • alloc指针:结构体指向的内存块
  • size:块的大小

API

  1. 创建内存池:建立头块,初始化图中的结构体参数
  2. 重置内存池:清空大块和在小块中分配的内存,其余都保留
  3. 销毁内存池:先清空大块再清空小块,因此大小的结构体链表存储在小块里
  4. 内存分配mp_malloc():(需通过对齐公式对齐分配)
    • 小块分配:尝试从current小块分配内存,失败则从下一个小块分配(failed到达4,就会更改current指针,保证malloc速度),如果所有小块都无法分配内存,则新申请一个小块
    • 大块分配:申请一个大块后,找一个大块的节点挂上去,如果找了4次还没找到(也是为了保证malloc速度),插入一个新的大块节点,然后挂上去
  5. 内存释放 mp_free():
    • 小块释放:通过地址值将对应的小块引用计数减一,计数为0后释放这个小块
    • 大块释放:大块可以直接释放(free速度慢点没关系),但大块节点留着给新的大块用

代码实现

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define PAGE_SIZE 4096
#define MP_ALIGNMENT 16
#define mp_align(n, alignment) (((n)+(alignment-1)) & ~(alignment-1))   // 对齐公式
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))

//每4k一block结点
struct mp_node_s {
    unsigned char *end;//块的结尾
    unsigned char *last;//使用到哪了
    struct mp_node_s *next;//链表
    int quote;//引用计数,为0就表示可以被回收了
    int failed;//失效次数
};

// 大块可以直接free,其信息节点保留用于记录下一个大块
struct mp_large_s {
    struct mp_large_s *next;//链表
    int size;//alloc的大小
    void *alloc;//大块内存的起始地址
};

struct mp_pool_s {
    struct mp_large_s *large;
    struct mp_node_s *head;    // 小块头
    struct mp_node_s *current;   // 当前正在用的小块
};

struct mp_pool_s *mp_create_pool(size_t size);

void mp_destroy_pool(struct mp_pool_s *pool);

void *mp_malloc(struct mp_pool_s *pool, size_t size);

void *mp_calloc(struct mp_pool_s *pool, size_t size);

void mp_free(struct mp_pool_s *pool, void *p);

void mp_reset_pool(struct mp_pool_s *pool);

void monitor_mp_poll(struct mp_pool_s *pool, char *tk);

//创建内存池:对于头块,以内存池结构体开始、其他的块都以小块结构体开始
struct mp_pool_s *mp_create_pool(size_t size) {
    struct mp_pool_s *pool;
    if (size < PAGE_SIZE || size % PAGE_SIZE != 0) {  // 小块,不足一页按一页分配
        size = PAGE_SIZE;
    }
    //分配4k以上不用malloc,用posix_memalign
    /*
        int posix_memalign (void **memptr, size_t alignment, size_t size);
     */

    int ret = posix_memalign((void **) &pool, MP_ALIGNMENT, size); //4K + mp_pool_s
    if (ret) {
        return NULL;
    }
    pool->large = NULL;
    pool->current = pool->head = (unsigned char *) pool + sizeof(struct mp_pool_s);    // 对于头快,head指向的 内存池结构体 的尾巴地址
    pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
    pool->head->end = (unsigned char *) pool + PAGE_SIZE;   // 内存池结构体和头块结构体都放头块内部
    pool->head->failed = 0;

    return pool;
}

// 重置内存池,分配的大小块不释放
void mp_reset_pool(struct mp_pool_s *pool) {
    struct mp_node_s *cur;
    struct mp_large_s *large;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc) {
            free(large->alloc);
        }
    }

    pool->large = NULL;
    pool->current = pool->head;
    for (cur = pool->head; cur; cur = cur->next) {
        cur->last = (unsigned char *) cur + sizeof(struct mp_node_s);
        cur->failed = 0;
        cur->quote = 0;
    }
}

//销毁内存池:由于大块结构体存储在小块中,需要先清空大块再清空小块
void mp_destroy_pool(struct mp_pool_s *pool) {
    struct mp_large_s *large;
    for (large = pool->large; large; large = large->next) {
        if (large->alloc) {
            free(large->alloc);
        }
    }

    struct mp_node_s *cur, *next;
    cur = pool->head->next;

    while (cur) {
        next = cur->next;
        free(cur);
        cur = next;
    }
    free(pool);
}

//size>4k
void *mp_malloc_large(struct mp_pool_s *pool, size_t size) {
    unsigned char *big_addr;
    int ret = posix_memalign((void **) &big_addr, MP_ALIGNMENT, size);   // 申请大块内存
    if (ret) {
        return NULL;
    }

    struct mp_large_s *large;
    //released struct large resume
    int n = 0;
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {    // 大块链表中找到一个空节点
            large->size = size;
            large->alloc = big_addr;
            return big_addr;
        }
        if (n++ > 3) {
            break;       // 为了避免过多的遍历,限制次数
        }
    }
    large = mp_malloc(pool, sizeof(struct mp_large_s));   // 没找到空位置,准备新插入一个大块节点
    if (large == NULL) {
        free(big_addr);
        return NULL;
    }
    large->size = size;     // 头插法
    large->alloc = big_addr;
    large->next = pool->large;
    pool->large = large;
    return big_addr;
}

// 新申请一个 4k小块
void *mp_malloc_block(struct mp_pool_s *pool, size_t size) {
    unsigned char *block;
    int ret = posix_memalign((void **) &block, MP_ALIGNMENT, PAGE_SIZE); // 申请4K小块内存
    if (ret) {
        return NULL;
    }
    struct mp_node_s *new_node = (struct mp_node_s *) block;   // 小块节点
    new_node->end = block + PAGE_SIZE;
    new_node->next = NULL;

    unsigned char *ret_addr = mp_align_ptr(block + sizeof(struct mp_node_s), MP_ALIGNMENT);    // 找到最近的对齐的地址

    new_node->last = ret_addr + size;
    new_node->quote++;

    struct mp_node_s *current = pool->current;   
    struct mp_node_s *cur = NULL;

    for (cur = current; cur->next; cur = cur->next) {
        if (cur->failed++ > 4) {     // 一个小块分配失败太多次,就从下一个小块分配
            current = cur->next;
        }
    }
    //now cur = last node
    cur->next = new_node;      // cur现在是最后一个小块
    pool->current = current;
    return ret_addr;
}

//分配内存
void *mp_malloc(struct mp_pool_s *pool, size_t size) {
    if (size <= 0) {
        return NULL;
    }
    if (size > PAGE_SIZE - sizeof(struct mp_node_s)) {
        //large
        return mp_malloc_large(pool, size);
    }
    else {
        //small
        unsigned char *mem_addr = NULL;
        struct mp_node_s *cur = NULL;
        cur = pool->current;
        while (cur) {
            mem_addr = mp_align_ptr(cur->last, MP_ALIGNMENT);    // 在小块内申请一块16字节对齐的内存
            if (cur->end - mem_addr >= size) {     // 分配成功
                cur->quote++;//引用+1
                cur->last = mem_addr + size;
                return mem_addr;
            }
            else {
                cur = cur->next;     // 这块分配失败,就下一块继续分配,为啥不failed++?
            }
        }
        return mp_malloc_block(pool, size);// 都分配失败,新申请一个小块
    }
}

void *mp_calloc(struct mp_pool_s *pool, size_t size) {
    void *mem_addr = mp_malloc(pool, size);
    if (mem_addr) {
        memset(mem_addr, 0, size);
    }
    return mem_addr;
}

//释放内存
void mp_free(struct mp_pool_s *pool, void *p) {
    struct mp_large_s *large;
    for (large = pool->large; large; large = large->next) {//大块
        if (p == large->alloc) {
            free(large->alloc);
            large->size = 0;
            large->alloc = NULL;
            return;
        }
    }
    //小块 引用-1,如果引用为0,就释放小块内存
    struct mp_node_s *cur = NULL;
    for (cur = pool->head; cur; cur = cur->next) {
//        printf("cur:%p   p:%p   end:%p\n", (unsigned char *) cur, (unsigned char *) p, (unsigned char *) cur->end);
        if ((unsigned char *) cur <= (unsigned char *) p && (unsigned char *) p <= (unsigned char *) cur->end) {
            cur->quote--;
            if (cur->quote == 0) {
                if (cur == pool->head) {
                    pool->head->last = (unsigned char *) pool + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
                }
                else {
                    cur->last = (unsigned char *) cur + sizeof(struct mp_node_s);
                }
                cur->failed = 0;
                pool->current = pool->head;
            }
            return;
        }
    }
}

void monitor_mp_poll(struct mp_pool_s *pool, char *tk) {
    printf("\r\n\r\n------start monitor poll------%s\r\n\r\n", tk);
    struct mp_node_s *head = NULL;
    int i = 0;
    for (head = pool->head; head; head = head->next) {
        i++;
        if (pool->current == head) {
            printf("current==>第%d块\n", i);
        }
        if (i == 1) {
            printf("第%02d块small block  已使用:%4ld  剩余空间:%4ld  引用:%4d  failed:%4d\n", i,
                   (unsigned char *) head->last - (unsigned char *) pool,
                   head->end - head->last, head->quote, head->failed);
        }
        else {
            printf("第%02d块small block  已使用:%4ld  剩余空间:%4ld  引用:%4d  failed:%4d\n", i,
                   (unsigned char *) head->last - (unsigned char *) head,
                   head->end - head->last, head->quote, head->failed);
        }
    }
    struct mp_large_s *large;
    i = 0;
    for (large = pool->large; large; large = large->next) {
        i++;
        if (large->alloc != NULL) {
            printf("第%d块large block  size=%d\n", i, large->size);
        }
    }
    printf("\r\n\r\n------stop monitor poll------\r\n\r\n");
}



int main() {
    struct mp_pool_s *p = mp_create_pool(PAGE_SIZE);    // 创建内存池
    monitor_mp_poll(p, "create memory pool");
#if 0
    printf("mp_align(5, %d): %d, mp_align(17, %d): %d\n", MP_ALIGNMENT, mp_align(5, MP_ALIGNMENT), MP_ALIGNMENT,
           mp_align(17, MP_ALIGNMENT));
    printf("mp_align_ptr(p->current, %d): %p, p->current: %p\n", MP_ALIGNMENT, mp_align_ptr(p->current, MP_ALIGNMENT),
           p->current);
#endif
    void *mp[30];
    int i;
    for (i = 0; i < 30; i++) {
        mp[i] = mp_malloc(p, 512);    
    }
    monitor_mp_poll(p, "申请512字节30个");

    for (i = 0; i < 30; i++) {
        mp_free(p, mp[i]);
    }
    monitor_mp_poll(p, "销毁512字节30个");

    int j;
    for (i = 0; i < 50; i++) {
        char *pp = mp_calloc(p, 32);
        for (j = 0; j < 32; j++) {
            if (pp[j]) {
                printf("calloc wrong\n");
                exit(-1);
            }
        }
    }
    monitor_mp_poll(p, "申请32字节50个");

    for (i = 0; i < 50; i++) {
        char *pp = mp_malloc(p, 3);
    }
    monitor_mp_poll(p, "申请3字节50个");


    void *pp[10];
    for (i = 0; i < 10; i++) {
        pp[i] = mp_malloc(p, 5120);
    }
    monitor_mp_poll(p, "申请大内存5120字节10个");

    for (i = 0; i < 10; i++) {
        mp_free(p, pp[i]);
    }
    monitor_mp_poll(p, "销毁大内存5120字节10个");

    mp_reset_pool(p);
    monitor_mp_poll(p, "reset pool");

    for (i = 0; i < 100; i++) {
        void *s = mp_malloc(p, 256);
    }
    monitor_mp_poll(p, "申请256字节100个");

    mp_destroy_pool(p);
    return 0;
}

参考链接