池式结构---内存池

chunfeng— 2025-04-12 20:25:58

池式结构---内存池-CSDN博客

内存池的介绍

        1.预先分配内存:内存池是在程序运行初期,预先从系统申请一块较大连续内存空间的池式结构。

        2.管理内存分配与回收:负责管理内存空间的分配与回收工作,当程序需要小块内存时,直接从内存池中取出合适的部分交付,而不是频繁向系统请求;当程序释放内存时,内存池将其回收,等待下次再分配,而非立刻归还给系统。

        3.提高效率与减少内存碎片:减少因频繁系统调用带来的开销,提高内存分配的效率。同时降低内存碎片的产生,因为系统调用分配内存相对耗时,且零散的内存分配易产生碎片,内存池通过集中管理内存,有效解决了这些问题。

        4.定制化内存管理:内存池可根据不同场景和需求,定制内存分配策略,以满足特定程序对内存的独特要求。

与malloc/free相比

        1.分配效率

        内存池:在程序启动时预先从操作系统申请一大块内存。后续程序需要内存时,直接从内存池中分配,无需再进行系统调用,大大减少了时间开销,显著提升了内存分配的效率。

        malloc/free:是 C 语言标准库提供的内存分配与释放函数。每次调用malloc时,它会向操作系统发起内存分配请求,这涉及用户态到内核态的切换,需要一定的时间开销。尤其在频繁分配小块内存的场景下,这种开销会不断累积,严重影响程序运行效率。

        2.内存碎片

        内存池:通过对预先申请的大块内存进行管理,能有效减少内存碎片。它可以采用特定的数据结构和算法,将内存分割成大小固定或按一定规则的小块进行分配。当小块内存被释放时,内存池能方便地将其重新整合,避免了内存碎片化问题,使内存空间始终保持较高的利用率。

        malloc/free:频繁的内存分配和释放极易产生内存碎片。当程序分配不同大小的内存块后又释放部分块时,内存空间会变得零散。

        外部碎片:表现为内存中存在许多分散的、不连续的小空闲区域,这些空闲区域分布在已分配的内存块之间

        内部碎片:表现为每个已分配的内存块内部存在未被利用的空闲部分,它是隐藏在已分配内存块内部的

内存管理组件

        1.jemalloc

        设计理念:设计理念是通用性和可扩展性。它不仅考虑了内存分配的性能,还注重对内存碎片的管理。

        内存管理策略

                多级分配:将内存划分为不同的大小类别,每个类别有独立的分配器。

                内存碎片管理:采用了先进的算法来减少内存碎片。它会尝试将空闲内存块合并,以提供更大的连续内存空间。

        使用场景:适用于对内存碎片敏感、需要处理各种大小内存分配的应用程序。

        2.tcmalloc

        设计理念:更侧重于为多线程应用程序提供高性能的内存分配。

        内存管理策略:

                线程本地缓存:每个线程都有自己的本地缓存,当线程需要分配内存时,首先会在本地缓存中查找可用的内存块,避免了线程之间的锁竞争。

                中央自由列表:用于管理全局的空闲内存块。当线程的本地缓存满了或者需要释放内存时,会将内存块放回中央自由列表。

        使用场景:更适合多线程应用程序,特别是那些对内存分配速度要求较高的场景。

内存管理        

        定长

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

// 定义内存页的大小为 4096 字节
#define MEM_PAGE_SIZE	0x1000  

// 定义内存池结构体
typedef struct mempool_s {
    // 每个内存块的大小
    int blocksize;
    // 空闲内存块的数量
    int freecount;
    // 指向空闲内存块链表的头指针
    char *free_ptr;
    // 指向内存池的起始地址
    char *mem;
} mempool_t; 

// 创建内存池
// 参数: m 为指向内存池结构体的指针, block_size 为每个内存块的大小
// 返回值: 成功返回 0, 失败返回 -1 或 -2
int memp_create(mempool_t *m, int block_size) {
    // 检查指针是否为空
    if (!m) return -1;
    // 设置每个内存块的大小
    m->blocksize = block_size;
    // 计算空闲内存块的数量
    m->freecount = MEM_PAGE_SIZE / block_size;
    // 分配内存页
    m->mem = (char *)malloc(MEM_PAGE_SIZE); 
    // 检查内存分配是否成功
    if (!m->mem) { 
        return -2;
    }
    // 将分配的内存页初始化为 0
    memset(m->mem, 0, MEM_PAGE_SIZE); 
    // 空闲内存块链表的头指针指向内存池的起始地址
    m->free_ptr = m->mem;

    int i = 0;
    char *ptr = m->mem;
    // 构建空闲内存块链表
    for (i = 0; i < m->freecount; i++) {
        // 将当前内存块的下一个指针指向下一个内存块的地址
        *(char **)ptr = ptr + block_size;
        // 移动指针到下一个内存块
        ptr = ptr + block_size;
    } 
    // 最后一个内存块的下一个指针置为 NULL
    *(char **)ptr = NULL;

    return 0;
}

// 销毁内存池
// 参数: m 为指向内存池结构体的指针
void memp_destory(mempool_t *m) {
    // 检查指针是否为空
    if (!m) return;
    // 释放内存池分配的内存
    free(m->mem);
}

// 从内存池中分配一个内存块
// 参数: m 为指向内存池结构体的指针
// 返回值: 成功返回分配的内存块的指针, 失败返回 NULL
void *memp_alloc(mempool_t *m) {
    // 检查指针是否为空或没有空闲内存块
    if (!m || m->freecount == 0) return NULL;
    // 获取当前空闲内存块链表的头指针
    void *ptr = m->free_ptr;
    // 更新空闲内存块链表的头指针为下一个空闲内存块
    m->free_ptr = *(char **)ptr;
    // 空闲内存块数量减 1
    m->freecount--;
    return ptr;
}

// 将内存块释放回内存池
// 参数: m 为指向内存池结构体的指针, ptr 为要释放的内存块的指针
void memp_free(mempool_t *m, void *ptr) {
    // 将释放的内存块插入到空闲内存块链表的头部
    *(char **)ptr = m->free_ptr;
    // 更新空闲内存块链表的头指针为释放的内存块
    m->free_ptr = (char *)ptr;
    // 空闲内存块数量加 1
    m->freecount++;
}

// 主函数, 用于测试内存池的功能
int main() {
    // 定义一个内存池结构体变量
    mempool_t m;
    // 创建内存池, 每个内存块大小为 32 字节
    memp_create(&m, 32);
    // 从内存池中分配一个内存块
    void *p1 = memp_alloc(&m);
    printf("memp_alloc : %p\n", p1);
    // 从内存池中分配一个内存块
    void *p2 = memp_alloc(&m);
    printf("memp_alloc : %p\n", p2);
    // 从内存池中分配一个内存块
    void *p3 = memp_alloc(&m);
    printf("memp_alloc : %p\n", p3);
    // 将内存块 p2 释放回内存池
    memp_free(&m, p2);
    return 0;
}

定长分配是一种内存管理策略,它将一块连续的内存空间划分为多个固定大小的内存块。

        1.内存池初始化

        在memp_create函数中,会分配一块大小为MEM_PAGE_SIZE的连续内存空间作为内存池。然后,根据指定的block_size将内存池划分为多个固定大小的内存块,并通过链表的方式将这些空闲内存块连接起来。

        2.内存分配

        在memp_alloc函数中,当需要分配内存时,会从空闲内存块链表的头部取出一个内存块,并将空闲内存块链表的头指针指向下一个空闲内存块。同时,空闲内存块的数量减 1。

        3.内存释放

        在memp_free函数中,当需要释放内存时,会将释放的内存块插入到空闲内存块链表的头部,并更新空闲内存块链表的头指针。同时,空闲内存块的数量加 1。

定长分配可以有效地减少内存碎片的产生,提高内存分配和释放的效率。因为每个内存块的大小是固定的,所以在分配和释放内存时不需要进行复杂的内存管理操作,只需要简单地操作链表即可。

      不定长

// 定义内存池节点结构体
typedef struct mp_node_s {
    // 指向当前节点未使用内存的起始位置
    unsigned char *last;
    // 指向当前节点内存的结束位置
    unsigned char *end;
    // 指向下一个内存池节点的指针
    struct mp_node_s *next;
} mp_node_t;

// 定义大内存块结构体
typedef struct mp_large_s {
    // 指向下一个大内存块的指针
    struct mp_large_s *next;
    // 指向实际分配的大内存块的指针
    void *alloc;
} mp_large_t;

// 定义内存池结构体
typedef struct mp_pool_s {
    // 内存池节点可分配的最大内存块大小
    size_t max; 
    // 指向内存池节点链表的头节点
    struct mp_node_s *head;
    // 指向大内存块链表的头节点
    struct mp_large_s *large;
} mp_pool_t;

// 创建内存池的函数声明
int mp_create(mp_pool_t *pool, size_t size);
// 销毁内存池的函数声明
void mp_destory(mp_pool_t *pool);
// 从内存池分配内存的函数声明
void *mp_alloc(mp_pool_t *pool, size_t size);
// 释放内存池内存的函数声明
void mp_free(mp_pool_t *pool, void *ptr);

// 创建内存池
// 参数 pool 为指向内存池结构体的指针,size 为内存池节点的初始大小
// 返回值:成功返回 0,失败返回 -1
int mp_create(mp_pool_t *pool, size_t size) {
    // 检查指针是否为空或者请求的大小是否合法
    if (!pool || size <= 0) return -1;
    // 分配一块指定大小的内存
    void *mem = malloc(size);
    // 将这块内存的起始部分作为一个内存池节点
    struct mp_node_s *node = (struct mp_node_s *)mem;
    // 计算该节点中可用于分配的内存起始位置
    node->last = (char *)mem + sizeof(struct mp_node_s);
    // 计算该节点的内存结束位置
    node->end = (char *)mem + size;
    // 该节点目前没有下一个节点
    node->next = NULL;
    // 内存池的头节点指向该节点
    pool->head = node;
    // 设置内存池节点可分配的最大内存块大小
    pool->max = size;
    // 初始时没有大内存块
    pool->large = NULL;
    return 0;
}

// 销毁内存池
// 参数 pool 为指向内存池结构体的指针
void mp_destory(mp_pool_t *pool) {
    mp_large_t *l;
    // 遍历大内存块链表
    for (l = pool->large; l; l = l->next) {
        // 如果该大内存块有实际分配的内存
        if (l->alloc) {
            // 释放该大内存块
            free(l->alloc);
        }
    }
    // 清空大内存块链表
    pool->large = NULL;
    // 指向内存池节点链表的头节点
    mp_node_t *node = pool->head;
    // 修正原代码中判断条件错误,这里应该是 node 不为空
    while (node) {
        // 保存下一个节点的指针
        mp_node_t *tmp = node->next;
        // 释放当前节点
        free(node);
        // 移动到下一个节点
        node = tmp;
    }
}

// 从内存池分配一个新的节点块
// 参数 pool 为指向内存池结构体的指针,size 为需要分配的内存大小
// 返回值:分配成功返回指向分配内存的指针,失败情况未处理完整
static void *mp_alloc_block(mp_pool_t *pool, size_t size) {
    // 原代码此处 mem 未定义,应修改为 malloc 分配内存
    void *mem = malloc(pool->max);
    struct mp_node_s *node = (struct mp_node_s *)mem;
    // 计算该节点中可用于分配的内存起始位置
    node->last = (char *)mem + sizeof(struct mp_node_s);
    // 计算该节点的内存结束位置
    node->end = (char *)mem + pool->max;
    // 该节点目前没有下一个节点
    node->next = NULL;
    // 获取当前可分配的内存起始位置
    void *ptr = node->last;
    // 更新可分配内存的起始位置
    node->last += size;
    // 找到内存池节点链表的尾节点
    mp_node_t *iter = pool->head;
    while (iter->next != NULL) {
        iter = iter->next;
    }
    // 将新节点插入到链表尾部
    iter->next = node;
    return ptr;
}

// 从内存池分配大内存块
// 参数 pool 为指向内存池结构体的指针,size 为需要分配的大内存块大小
// 返回值:分配成功返回指向分配内存的指针,失败返回 NULL
static void *mp_alloc_large(mp_pool_t *pool, size_t size) {
    // 检查指针是否为空
    if (!pool) return NULL;
    // 直接调用系统的 malloc 分配大内存块
    void *ptr = malloc(size);
    // 检查是否分配成功
    if (ptr == NULL) return NULL;
    mp_large_t *l;
    // 遍历大内存块链表,查找是否有未使用的节点
    for (l = pool->large; l; l = l->next) {
        if (l->alloc == NULL) {
            // 如果找到未使用的节点,将其指向新分配的大内存块
            l->alloc = ptr;
            return ptr;
        }
    }
    // 如果没有找到未使用的节点,从内存池分配一个大内存块结构体
    l = (mp_large_t *)mp_alloc(pool, sizeof(mp_large_t));
    if (l == NULL) {
        // 如果分配失败,释放之前分配的大内存块
        free(ptr);
        return NULL;
    }
    // 将新分配的大内存块结构体指向实际的大内存块
    l->alloc = ptr;
    // 将新的大内存块结构体插入到大内存块链表头部
    l->next = pool->large;
    pool->large = l;
    return ptr;
}

// 从内存池分配内存
// 参数 pool 为指向内存池结构体的指针,size 为需要分配的内存大小
// 返回值:分配成功返回指向分配内存的指针,失败返回 NULL
void *mp_alloc(mp_pool_t *pool, size_t size) {
    // 如果请求的内存大小超过内存池节点可分配的最大大小
    if (size > pool->max) {
        // 调用分配大内存块的函数
        return mp_alloc_large(pool, size);
    } 
    // 请求的内存大小小于等于内存池节点可分配的最大大小
    void *ptr = NULL;
    // 指向内存池节点链表的头节点
    mp_node_t *node = pool->head;
    do {
        // 检查当前节点是否有足够的空间
        if (node->end - node->last > size) {
            // 获取当前可分配的内存起始位置
            ptr = node->last;
            // 更新可分配内存的起始位置
            node->last += size;
            return ptr;
        } 
        // 移动到下一个节点
        node = node->next;
    } while (node);
    // 如果所有节点都没有足够的空间,分配一个新的节点块
    return mp_alloc_block(pool, size);
}

// 释放内存池中的内存
// 参数 pool 为指向内存池结构体的指针,ptr 为要释放的内存指针
void mp_free(mp_pool_t *pool, void *ptr) {
    mp_large_t *l;
    // 遍历大内存块链表
    for (l = pool->large; l; l = l->next) {
        // 如果找到要释放的大内存块
        if (l->alloc == ptr) {
            // 释放该大内存块
            free(l->alloc);
            // 将该大内存块结构体的 alloc 指针置为 NULL
            l->alloc = NULL;
            return;
        }
    }
    // 对于小内存块,这里未做具体释放操作,因为小内存块是按节点整体管理
}

 核心思想是将内存分配分为小内存块和大内存块两种情况分别处理,以提高内存分配和释放的效率,减少内存碎片。

        1.内存池结构

            mp_pool_t:这是内存池的核心结构体,包含三个重要成员

                        max:表示内存池节点可分配的最大内存大小

                        head:指向内存池节点链表的头节点,这些节点用于分配小内存块

                        large:指向大内存块链表的头节点,用于管理超过max大小的大内存块

          mp_node_t:小内存块结构体,用于管理小内存块,last指针指向当前节点未使用内存的起始位置,end指针指向当前节点内存的结束位置,next指针指向下一个节点。

          mp_large_t:大内存块结构体,alloc指针指向实际分配的大内存块,next指针指向下一个大内存块。

        2.内存分配

        小内存块分配:当请求的内存大小size小于等于pool->max时,会遍历内存池节点链表,检查每个节点是否有足够的空间。如果有,则从该节点分配内存并更新last指针;如果所有节点都没有足够的空间,则调用mp_alloc_block函数分配一个新的节点块。

        大内存块分配:当请求的内存大小size大于pool->max时,直接调用系统的malloc函数分配大内存块。然后会先遍历大内存块链表,查找是否有未使用的节点,如果有则将其指向新分配的大内存块;如果没有,则从内存池分配一个大内存块结构体,并将其插入到大内存块链表头部。

        3.内存释放

        大内存块释放:当调用mp_free函数释放内存时,会先遍历大内存块链表,找到要释放的大内存块,然后调用free函数释放该大内存块,并将该大内存块结构体的alloc指针置为 NULL。

        小内存块释放:小内存块是按节点整体管理的,在销毁内存池时会统一释放所有节点的内存。

使用不定长内存池可以高效地处理不同大小的内存分配请求,同时减少了频繁调用系统mallocfree带来的开销和内存碎片问题。

 

...全文
33 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

440

社区成员

发帖
与我相关
我的任务
社区描述
零声学院,目前拥有上千名C/C++开发者,我们致力将我们的学员组织起来,打造一个开发者学习交流技术的社区圈子。
nginx中间件后端 企业社区
社区管理员
  • Linux技术狂
  • Yttsam
  • 零声教育-晚晚
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

请新加入的VIP学员,先将自己参加活动的【所有文章】,同步至社区:

【内容管理】-【同步至社区-【零声开发者社区】

试试用AI创作助手写篇文章吧