赞
踩
应用程序直接对使用的内存进行管理,避免频繁的从堆上申请释放内存,可以做到一次申请,多次分配。内存池主要是针对小块。
内存池管理的是堆内存。进程开始运行的时候,划分出一块内存
对于服务器,如果不段有客户端连接,并且客户端不断发送消息,对于每个连接的每个消息,服务器都需要去malloc/free内存。如果服务器需要7*24运行,时间久了,就会产生很多内存碎片,没有整块,最后就有可能malloc比较大的内存的时候失败。最后进程就会coredump。
这种问题,出现了很难解,因为程序要运行很长时间才会出现。解决方案就是使用内存池。
频繁的malloc/free, 会造成哪些问题?
典型的场景,服务器处理网络io。message较大的时候,recv、parse后,需要push到另一个thread进行处理。使用栈内存不合适,需要申请堆内存。recv的时候申请buffer,send后,释放buffer。每来一个消息,都需要申请、释放内存。
每次malloc内存后,加入到一个内存池链表中,free的时候不释放,只是修改flag标志,表示当前内存块是空闲的。一定程度解决内存碎片的问题。
存在的问题
1)内存块越分越小。
如果一个内存块64字节,现在应用程序需要54字节,那么剩余的10字节又会加入到内存池链表的尾部。就会造成出现很多小块内存,越分越小,出现很多没办法使用的小块内存。链表也会越来越长。
2. 分配固定大小的不同的块
内存池内分别有16 byte,32byte...,这种固定大小的内存块。应用程序要申请内存,到相应大小的块中去获取。如果要申请大于1024 byte的内存,直接就申请一整块。这样就能够解决出现内存块越分越小的问题。
3. 用hash table
这种方法有什么缺点?
1) 查找速度慢,
malloc的时候,需要查找空闲的内存块,即flag为0
free的时候需要查找,设置flag = 0.
free的时候,参数加上size,就可以在hash table中找到相应的slot。
解决方法:
a. 对于malloc时查找,可以将每一个slot中的内存块分为两个链表,一个是已使用的,一个是未使用的。
b. free时候的查找,可以用hash,rbtree。
2)内存存在浪费,会存在间隙。影响小块的回收。
小块有没有必要回收?小块内存回收是一个极其麻烦的事情。
如果一个连接对应一个内存池,连接的生命周期不会很长,可以不回收小块内存。
如果要使用全局内存池,可以使用jemalloc、tcmalloc。
解决小块回收的方法:
对于16bytes的slot,下面挂的是4k的内存块, 即一开始就分配一个整块,每一个从这个内存块里面分配16bytes,如果4k用完了,接着再申请4k内存块,挂到链表上。
我们的实现比较粗犷,对于小块内存,没有单独回收,要回收统一回收,不单独回收一个块内的某个小块内存。对于大块,进行回收。
内存池里面保持两个list,一个是小块,一个是大块。小块大小固定是4k,大块用于大于4k的内存。如果申请小于4k的内存,直接从小块进行分配,一次申请好4k,之后在这个基础上进行分配;申请大于4k的内存,则使用大块,用多少申请多少,大块的大小不是固定的。
对于一个网络连接,我们create一个内存池,等到连接断开,destroy内存池。
- #include <stdlib.h>
-
-
- #define ALIGNMENT 8
-
-
- typedef struct _mp_node_s {
-
- unsigned char *last; // 内存块中未分配内存的首地址
- unsigned char *end; // 内存块的尾地址
-
- struct _mp_node_s *next;
-
- } mp_node_s;
-
-
- typedef struct _mp_large_s {
-
- struct _mp_large_s *next;
- void *alloc;
-
- } mp_large_s;
-
-
- typedef struct _mp_pool_s {
-
- mp_node_s *small;
- mp_large_s *large;
-
- int size;
-
- } mp_pool_s;
-
- void *mp_malloc(mp_pool_s *pool, int size);
- mp_pool_s *mp_create_pool(int size) {
-
- mp_pool_s *pool;
-
- int ret = posix_memalign((void **)&pool, ALIGNMENT, size + sizeof(mp_pool_s));
- if (ret) return NULL;
-
- pool->small = (mp_node_s *)(pool + 1);
-
- pool->small->last = (unsigned char *)(pool->small + 1);
- pool->small->end = (unsigned char *)pool + size + sizeof(mp_pool_s);
- pool->small->next = NULL;
-
- pool->large = NULL;
-
- return pool;
-
- }
-
- void mp_destroy_pool(mp_pool_s *pool) {
-
- mp_large_s *large;
-
- for (large = pool->large; large; large = large->next) {
- if (large->alloc) {
- free(large->alloc);
- }
- }
-
- mp_node_s *small, *next;
-
- for (small = pool->small; small;) {
-
- next = small->next;
- free(small);
- small = next;
- }
-
- free(pool);
- }
-
-
- static void *mp_malloc_large(mp_pool_s *pool, int size) {
-
- mp_large_s *large = (mp_large_s *)mp_malloc(pool, sizeof(mp_large_s));
-
- int ret = posix_memalign((void **)&large->alloc, ALIGNMENT, size);
- if (ret) return NULL;
-
- return large->alloc;
-
- }
-
- static void *mp_malloc_small(mp_pool_s *pool, int size) {
-
- mp_node_s *node = NULL;
- int ret = posix_memalign((void **)&node, ALIGNMENT, pool->size);
- if (ret) return NULL;
-
- node->next = pool->small;
- pool->small = node;
-
- node->last = (unsigned char *)node + sizeof(mp_node_s);
- node->end = (unsigned char *)node + pool->size;
-
- return node->last;
-
-
- }
-
- void *mp_malloc(mp_pool_s *pool, int size) {
- if (size < pool->size) {
- mp_node_s *node = pool->small;
-
- if (size < node->end - node->last) { // 需要考虑字节对齐
- unsigned char *m = node->last;
- node->last = m + size;
- return m;
-
- } else {
- return mp_malloc_small(pool, size);
- }
- } else {
- return mp_malloc_large(pool, size);
- }
-
- }
-
- void *mp_free(mp_pool_s *pool, void *p) {
- mp_large_s *large = NULL;
- for (large = pool->large; large; large = large->next) {
- if (large->alloc == ()p) {
- free(large->alloc);
- large->alloc = NULL;
- break;
- }
- }
- }
-
-
- int main() {
-
-
-
- }
加锁来保证内存池线程安全,锁的粒度需要把握好。
如果是单线程reactor,或者没个线程一个reactor,也就是一个连接只在一个线程处理,不需要考虑加锁,一个连接一个线程池,是线程安全的。
如果要使用全局内存池,可以使用开源内存池
使用的时候,加上一个宏定义,不需要改源代码,malloc/free就被hook住,使用jemalloc/tcmalloc的函数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。