内容:
本文将介绍几种常用的内存池技术的实现,这是我最近学习各大开源的内存池技术遗留下来的笔记,其主要内容包括:
- STL内存池以及类STL内存池实现
- Memcached内存池实现
- 固定规格内存池实现
- Nginx内存池实现
一.类STL的内存池实现方式
SGI STL的内存池分为一级配置器和二级配置器,
一级配置器主要处理分配空间大小大于128Byte的需求,其内部实现就是直接使用malloc realloc 和free.
二级配置器则使用使用free_list的数组链表的方式来管理内存,SGI的Allocate最小的分辨单位为8Byte,其free_list数组存着8*n(n=1...16)大小内存的首地址,大小同样的内存块使用链表的形式相连
free_list[0] --------> 8 byte
free_list[1] --------> 16 byte
free_list[2] --------> 24 byte
free_list[3] --------> 32 byte
... ...
free_list[15] -------> 128 byte
因为其对内存的管理的最小分辨度为8Byte,所以当我们申请的内存空间不是8的倍数的时候,内存池会将其调整为8的倍数大小,这叫内存对齐。当然这也免不了带来内存浪费,例如我们只需要一个10Byte的大小,内存池经过内存对齐后,会给我们一个16Byte的大小,而剩余的6Byte,在这次使用中根本没有用到。(对于chunk_allocate的优化请见探究操作系统的内存分配(malloc)对齐策略一文的末尾处)
类STL的内存池一般都有如下API
void* refill(size_t n) //内部函数,用于allocate从free_list中未找到可使用的块时调用
这种内存池的工作流程大致如下:
- 外部调用 allocate向内存池申请内存
- allocate通过内存对齐的方式在free_list找到合适的内存块链表头
- 判断链表头是否为NULL,为NULL则表示没有此规格空闲的内存,如果不为NULL,则返那块内存地址,并将此块内存地址移除它对应的链表
- 如果为NULL,则调用refill在freelist上挂载20个此规格的内存空间(形成链表),也就是保证此规格的内存空间下次请求时够用
- refill的内部调用了chunk_alloc函数,chunk_alloc的职责就是负责内存池的所有内存的生产,在生产的时候他为了保证下次能有内存用,所以会将空间*2,所以这个申请流程总的内存消耗为:(对需求规格内存对齐后的大小)*20*2
- 当第一次调用chunk_alloc(32,10)的时候,表示我要申请10块__Obje(free_list), 每块大小32B,此时,内存池大小为0,从堆空间申请32*20的大小的内存,把其中32*10大小的分给free_list[3]。
- 我再次申请64*5大小的空间,此时free_list[7]为0, 它要从内存池提取内存,而此时内存池剩下320B,刚好填充给free_list[7],内存池此时大小为0。
- 第三次请求72*10大小的空间,此时free_list[8]为0,它要从内存池提取内存,此时内存池空间不足,再次从堆空间申请72*20大小的空间,分72*10给free_list用。
首次申请20Byte后的状态图:
在未设置预分配的STL内存池中,某个中间状态的整体图
由于STL源码可阅读性不强,各种宏等等满目不堪,所以我这里就不贴SGI 的源码了,我在这里贴一个简单易懂的山寨版本, 基本的思路是一模一样的,这个实现没有了一级和二级配置器,而是在需要的时候直接malloc或者从free_list找。

二.MemCached内存池实现
与类STL内存池不同的是, 用于缓存的内存池不是解决小对象的内存分配可能导致堆内存碎片多的问题,缓存内存池要为缓存系统的所有存储对象分配空间,无论大小。因为缓存系统通常对其占用的最大内存有限制,所以也就不能在没有空间用的时候随便malloc来实现了。 MemCached的内存池的基本想法是避免重复大量的初始化和清理操作。
特定大小的chunk的组。
Memcached的内存分配以page为单位,默认情况下一个page是1M ,可以通过-I参数在启动时指定。如果需要申请内存 时,memcached会划分出一个新的page并分配给需要的slab区域。Memcached并不是将所有大小的数据都放在一起的,而是预先将数据空间划分为一系列slabs,每个slab只负责一定范围内的数据存储,其大小可以通过启动参数设置增长因子,默认为1.25,即下一个slab的大小是上一个的1.25倍。如 下图,每个slab只存储大于其上一个slab的size并小于或者等于自己最大size的数据。如下图所示,需要存储一个100Bytes的对象时,会选用112Bytes的Slab Classes
基于这种实现的内存池也会遇到STL内存池一样的问题,那就是资源的浪费,我只需要100Byte的空间,你却给了我128Bytes,剩余的28Bytes就浪费了
其主要API:
关于MemCached还有个问题需要解释下,在预分配的场景下,有的同事认为MemCached不适合大量存储某个特定大小范围内的对象,他们认为预分配的条件下,每个SlabClasses的总大小是固定的(为一个Page),其实不是,MemCached预分配并不会消耗掉所有的内存,在请求空间的时候,如果发现这个型号的Chunks都被用完了,就会新增一个分页到这个Slab Classes,所以是不会出现那位同事说的那个问题的...(可见代码slabs.c中do_slabs_alloc函数中do_slabs_newslab的调用)
三.固定大小内存池
上面两种内存池的实现,都会造成一定程度的内存浪费,如果我存的对象大小基本是固定的,尽管有很多不同的对象,有没有不会浪费内存的的简单方式呢?
既然需要存的对象大小是固定的,那么我们的内存池对于内存的管理可以这样实现:
};
char* chunk_alloc(size_t __size, int& __nobjs)//内部函数,用于分配一个大块
void* refill(size_t n) //内部函数,用于allocate从free_list中未找到可使用的块时调用
};
这样的实现对于这个特定的需求非常好用,不回浪费掉剩余空间,但是这样的实现局限性就高了,我们不能用这个内存池来存储大小不定的对象(如string),如果用了,此内存池形同虚设,并且还浪费内存,所以具体怎么选择还是要看需求来定...
http://blog.youkuaiyun.com/v_july_v/article/details/7040425http://bbs.chinaunix.net/thread-3626006-1-1.html;http://blog.youkuaiyun.com/livelylittlefish/article/details/6586946;http://blog.chinaunix.net/space.php?uid=7201775;淘宝数据共享平台博客:http://www.tbdata.org/archives/1390
参考资料:http://www.cnblogs.com/sld666666/archive/2010/07/01/1769448.html
http://www.cnblogs.com/sniperHW/archive/2012/04/02/2429612.html
http://www.cnblogs.com/sniperHW/archive/2012/04/02/2429615.html
http://www.cnblogs.com/sniperHW/archive/2012/04/02/2429617.html
http://www.cnblogs.com/sniperHW/archive/2012/04/02/2429620.html