猎头公司注册条件长沙seo顾问
目录
内存池的介绍
与malloc/free相比
1.分配效率
2.内存碎片
3.使用场景
内存管理组件
1.jemalloc
2.tcmalloc
内存管理
定长
1.内存池初始化
2.内存分配
3.内存释放
不定长
1.内存池结构
2.内存分配
3.内存释放
内存池的介绍
1.预先分配内存:内存池是在程序运行初期,预先从系统申请一块较大连续内存空间的池式结构。
2.管理内存分配与回收:负责管理内存空间的分配与回收工作,当程序需要小块内存时,直接从内存池中取出合适的部分交付,而不是频繁向系统请求;当程序释放内存时,内存池将其回收,等待下次再分配,而非立刻归还给系统。
3.提高效率与减少内存碎片:减少因频繁系统调用带来的开销,提高内存分配的效率。同时降低内存碎片的产生,因为系统调用分配内存相对耗时,且零散的内存分配易产生碎片,内存池通过集中管理内存,有效解决了这些问题。
4.定制化内存管理:内存池可根据不同场景和需求,定制内存分配策略,以满足特定程序对内存的独特要求。
与malloc/free相比
1.分配效率
内存池:在程序启动时预先从操作系统申请一大块内存。后续程序需要内存时,直接从内存池中分配,无需再进行系统调用,大大减少了时间开销,显著提升了内存分配的效率。
malloc/free:是 C 语言标准库提供的内存分配与释放函数。每次调用malloc时,它会向操作系统发起内存分配请求,这涉及用户态到内核态的切换,需要一定的时间开销。尤其在频繁分配小块内存的场景下,这种开销会不断累积,严重影响程序运行效率。
2.内存碎片
内存池:通过对预先申请的大块内存进行管理,能有效减少内存碎片。它可以采用特定的数据结构和算法,将内存分割成大小固定或按一定规则的小块进行分配。当小块内存被释放时,内存池能方便地将其重新整合,避免了内存碎片化问题,使内存空间始终保持较高的利用率。
malloc/free:频繁的内存分配和释放极易产生内存碎片。当程序分配不同大小的内存块后又释放部分块时,内存空间会变得零散。
外部碎片:表现为内存中存在许多分散的、不连续的小空闲区域,这些空闲区域分布在已分配的内存块之间
内部碎片:表现为每个已分配的内存块内部存在未被利用的空闲部分,它是隐藏在已分配内存块内部的
3.使用场景
内存池:适合对内存分配效率和内存管理要求较高的场景。
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;}// 将分配的内存页初始化为 0memset(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;// 空闲内存块数量减 1m->freecount--;return ptr;
}// 将内存块释放回内存池
// 参数: m 为指向内存池结构体的指针, ptr 为要释放的内存块的指针
void memp_free(mempool_t *m, void *ptr) {// 将释放的内存块插入到空闲内存块链表的头部*(char **)ptr = m->free_ptr;// 更新空闲内存块链表的头指针为释放的内存块m->free_ptr = (char *)ptr;// 空闲内存块数量加 1m->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 指针置为 NULLl->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。
小内存块释放:小内存块是按节点整体管理的,在销毁内存池时会统一释放所有节点的内存。
使用不定长内存池可以高效地处理不同大小的内存分配请求,同时减少了频繁调用系统
malloc
和free
带来的开销和内存碎片问题。