内存池设计基本思路
设计内存池,以减少因频繁调用malloc造成堆中造成的内存碎片
基本结构
- 选取key提升malloc效率:
对于一块堆内存,申请时指定
大小,而释放时指定地址,因此,采用内存大小做key,malloc时更块,采用地址做key,free时更快。但在实际项目中,malloc的效率比free的效率更重要,因此采用大小做key。
- 采用红黑树提升free效率: 由于释放内存时不知道该内存块的大小,遍历起来很慢,所以将每个key对应的链表组织成红黑树;free时不释放到内存,设置为空标记即可
应用问题
- 线程安全问题:加锁
- 内存回收策略:
- 大块拆小块:128k的可以全部拆成16k的,也可以拆成1个64k,1个32k,2个16k
- 小块合大块:由于需要小块相邻,可以直接把小块全部释放,然后重新向操作系统申请大块
现有内存表
现在一般都是用一个链表组织内存块,如STL内存池:
缺点:
- 查找慢
- 小块回收麻烦,都不是挨着的
- 内存表的所有节点都是碎片,它们存在哪里,要怎么释放
使用场景:
- 全局内存池:整个程序就用一个内存池 (推荐jemalloc、tcmalloc)
- 一个长连接做一个内存池 (不用回收小块,反正时间不长)
- 消息或者短连接不用做内存池,浪费
内存池设计
这里只针对一个长连接做一个内存池,小内存块不立即释放的,先上图:
解决的问题
- 查找慢:
- malloc时,直接在小块的last处分配即可;
- free时,遍历每个块,如果这个指针p的地址大于小块的起始地址,小于小块的end指针,说明这个要释放的地址就在这个小块,不用释放这个p指向的内存,将这个小块的引用计数减一即可,当引用计数为0时,直接释放这个小块。
- 小块回收麻烦:
- malloc的小块内存都是挨着的,释放方便
- 内存表节点碎片:
- 节点都存在小块里,释放小块时,这个节点所占的内存也会跟着释放
结构讲解
- 内存池结构体
- 放在第一个小块中(从第二个小块开始,都没这个结构体了)
- large指针:指向大块链表
- small指针:指向小块链表
- current指针:指向当前分配内存的小块
- 小块结构体:
- 以一页的大小作为小块
- last指针:指向当前小块未分配内存的起始地址
- end指针:指向当前小块内存块的结束地址
- next指针:指向下一个小块
- quote引用计数:在这个小块malloc成功一次加一,free一次减一,为0时代表这个小块可释放
- failed计数:这个内存块malloc失败一次加一,达到指定次数(如4)时,不再在这个小块分配内存
- 大块结构体:
- next指针:下一个大块
- alloc指针:结构体指向的内存块
- size:块的大小
API
- 创建内存池:建立头块,初始化图中的结构体参数
- 重置内存池:清空大块和在小块中分配的内存,其余都保留
- 销毁内存池:先清空大块再清空小块,因此大小的结构体链表存储在小块里
- 内存分配mp_malloc():(需通过对齐公式对齐分配)
- 小块分配:尝试从current小块分配内存,失败则从下一个小块分配(failed到达4,就会更改current指针,保证malloc速度),如果所有小块都无法分配内存,则新申请一个小块
- 大块分配:申请一个大块后,找一个大块的节点挂上去,如果找了4次还没找到(也是为了保证malloc速度),插入一个新的大块节点,然后挂上去
- 内存释放 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;
}