内存池的应用及其必要性
应用场景
当客户端连接上服务端的时候,服务端会准备一部分的堆区用来做消息保留。当一个连接成功之后,服务器会在堆区为其分配一段属于这个连接的内存,当连接关闭之后,所分配的内存也随之释放。但是当连接量较大且过于频繁时,不可避免地对内存进行频繁的分配和释放。这会导致堆区出现小窗口,也就是堆区碎片化。 1 全局的内存池 用jemalloc或者tcmalloc 开源封装好的 2 一个链接做一个内存池 这种场景,链接的生命周期没有那么长,小块没有必要回收 3 每一个消息做一个内存池 ----X 这种方式没有意义。必要性
堆区出现碎片化会怎么样?
长时间工作会出现不可查的BUG
无法分配较大且整块的内存,malloc会返回NUL
开源内存池组件
jemalloc:
tcmalloc:内存回收的机制有一些小问题
使用方法:直接在前面加上一个宏定义 malloc 和free就被hook住了。然后在自己写的代码程序中调用malloc,实际上调用的是内存池开源库中的malloc和free。
这两种库的具体使用方法请自行去百度上搜索即可。
内存池设计原理
首先给大家贴上两张大图贴。
(画的比较丑)
场景:在一段很干净的堆区上,如何实现能避免内存碎片化的内存池?
使用链表将分配出来的一块一块内存在堆区连接起来,设置flag(是否被使用),让链表节点上的各段内存慢慢各自扩张。
单独使用链表会出现什么问题?
内存块会被划分得越来越小,链表会变得越来越长,直到不能划分出更大得内存。
所以加入了固定内存的想法的设计
对于分配小段内存时,将小段内存进行固定划分,如下
16bytes
32bytes
64bytes
128bytes
256byts
512bytes
根据自己需要内存
如上图将各个范围的空间大小用一个数组组织起来。
首先以16byte为例。
首先一次性分配4k的内存空间挂载到16bytes上面,然后每一次需要一个16个字节空间时,直接从4k的空间中取。直到16byte上这个4k空间用完后,在分配一个4k空间用链表的方式串起来。
当链表在一个数组节点串了很多时,会发现查找空间速度会很慢。
1.查找慢
分配时查找
释放时候查找
2.块与块之间也会出现间隙
无法将块合并
小块回收麻烦
所以最后得出自定义固定小块和随机大块的内存池模型
//大块节点
//成员:1指向下一个大块节点的指针,2指向自己节点管理的内存空间
struct mp_large_s {
struct mp_lagge_s *next;
void *alloc;
};
//小块节点
struct mp_node_s {
unsigned char *last;//指向小块节点管理内存中未使用空间的首地址
unsigned char *end;//指向此节点管理空间的最后位置
struct mp_node_s *next;//指向下一个链表节点
size_t failed;
};
struct mp_pool_s {
size_t max;
struct mp_node_s *curent;//当前还未满小块节点
struct mp_large_s *large;//大块节点的首节点
struct mp_node_s head[0];//小块节点的首标志
};
一个mp_pool_s 内存池管理器中 有一个指向大块节点链表的指针,根据max大小来区分用户申请的是大块内存还是小块内存。
流程图
内存池代码实现api
内存池的创建
struct mp_pool_s *mp_create_pool(size_t size) {
struct mp_pool_s *p;
int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));//malloc 如果一次性分配4k 可能会失败,但是posix_memalign可以
if (ret) {
return NULL;
}
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
p->head->failed = 0;
return p;
}
内存池销毁
void mp_destory_pool(struct mp_pool_s *pool) {
struct mp_node_s *h, *n;
struct mp_large_s *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
free(l->alloc);
}
}
h = pool->head->next;
while (h) {
n = h->next;
free(h);
h = n;
}
free(pool);
}
小内存块创建
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *h = pool->head;
size_t psize = (size_t)(h->end - (unsigned char *)h);
int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
if (ret) return NULL;
struct mp_node_s *p, *new_node, *current;
new_node = (struct mp_node_s*)m;
new_node->end = m + psize;
new_node->next = NULL;
new_node->failed = 0;
m += sizeof(struct mp_node_s);
m = mp_align_ptr(m, MP_ALIGNMENT);//用来做对齐
new_node->last = m + size;
current = pool->current;
for (p = current; p->next; p = p->next) {
if (p->failed++ > 4) {
current = p->next;
}
}
p->next = new_node;
pool->current = current ? current : new_node;
return m;
}
大内存块创建
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if (p == NULL) return NULL;
size_t n = 0;
struct mp_large_s *large;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n ++ > 3) break;
}
large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
用户调用(向内存池申请空间的接口)
void *mp_alloc(struct mp_pool_s *pool, size_t size) {
unsigned char *m;
struct mp_node_s *p;
if (size <= pool->max) {
p = pool->current;
do {
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
} while (p);
return mp_alloc_block(pool, size);
}
return mp_alloc_large(pool, size);
}
源代码地址及测试代码
https://github.com/xiaoyeyihao/xioayeyihao.github.io/tree/master/mempool