本人在转发的博文《内存池的设计和实现》中,详细阐述了系统默认内存分配函数malloc/free的缺点,以及进行内存池设计的原因,在此不再赘述。通过对Nginx内存池以及《内存池的设计和实现》的分析后,现提出一种性能更优(申请/释放内存时间复杂度为O(1))的内存池的设计方案。如有不妥之处,欢迎指正!如有其他的内存池的设计方案,欢迎共同分享和探讨。【注意:LINUX内核、NGINX、MEMCACHE使用SLAB机制内存分配方案】
/* 内存池结构体 */ typedef struct { int unitsize; /* 内存单元大小,即unit的大小 */ int initnum; /* 初始内存单元的数目 */ int grownum; /* 每次新增内存单元的数目 */ int totalnum; /* 内存单元总数 */ memblock_t *block; /* memblock_t链表头 */ char *idleunit; /* 空闲内存单元链表头 */ #if defined(__MEMPOOL_LOCK__) spinlock_t lock; /* 自旋锁:使用自旋锁有效避免CPU切换的开销 */ #endif /*__MEMPOOL_LOCK__*/ }mempool_t;
代码1 内存池结构
/* 内存块结构体 */ typdef struct { int unitnum; /* 内存块总数 */ int idlenum; /* 空闲内存块数 */ mempool_t *pool; /* 所属池:所属mempool_t */ char *lastunit; /* 结束块地址(此变量可删除) */ memblock_t *next; /* 下一个memblock_t */ }memblock_t;
代码2 内存块结构
/* 内存单元信息 */ typedefstruct { memblock_t *block; /* 所属块:内存单元所属memblock_t */ char *next; /* 下一块内存块地址 */ }memunit_info_t;
代码3 内存单元信息
内存池的总体结构图为:
图1 总体结构图
此内存池的运行机制如下:
图2 Mempool_t数组
[注:为提高效率,通过数组存储Mempool_t,在申请内存空间时,可通过偏移量快速定位使用哪个大小的内存池]
图3 Memblock_t链表
图4 空闲内存单元链表
[说明:Memblock_t中的用红色数字标记的内存单元代表已被分配内存单元,
用绿色数字标记的内存单元代表空闲内存单元]
图5 内存申请图
[注:当申请的内存空间size比所有的内存单元都大时,
则通过malloc()向OS申请size+sizeof(memunit_info_t)的内存空间]
图6 内存释放图
图7 内存单元内部结构
说明:
-> 1. 每个内存单元的内部结构:memunit_info_t结构+unitsize大小的空间。每个内存单元的大小为:sizeof(memunit_info_t)+unitsize;
-> 2. idleunit指向的是内存单元的data;空闲内存单元的next指向的是后继内存单元的data,无后继则为NULL;已分配的内存单元的next始终为NULL。
-> 3. 内存单元的block指向宿主Memblock_t,这可快速的确定对当前内存单元属于哪个Memblock_t,再通过Memblock_t中的pool,可快速获知属于哪个Mempool_t。
-> 4. 在分配内存时,返回给用户的是data的地址,而不是内存单元的地址。
图8 所属Mempool_t
通过对以上几点的分析,可知此内存池有以下优缺点:
优点:
1. 定位内存池的时间复杂度为O(1)
内存单元可申请使用的空间依次为8、16、32、64、128、256、512、1024 ..., 因此,定位内存池的算法:(n为内存池数组下标)
if(size > 8) { shift=1; for(s=size-1; s>=1; ++shift) { NULL; } /* 计算位移 */ n = shift - 3; /* 计算角标 */ } else { n = 0; }
2. 申请和释放内存的时间复杂度为O(1)
3. 有效减少内存碎片
4. 较小的互斥粒度:申请空间时,每次只锁住对应的mempool_t的内存池,依然可以申请其他size的内存池空间[注:如果再加入为每个线程分配一个内存池对象的机制,则可达到零互斥零竞争。这样的话,可不使用互斥机制,同时能够进一步提高性能]
5. 内存空间可动态扩展。
缺点:
1. 内存单元的实际大小要比unitsize多sizeof(memunit_info_t)个字节
2. 空闲内存单元链表中的内存单元是乱序串联的,因此会造成即使空闲内存单元个数超过单个Memblock_t内存单元总数时,操作系统可能依然无法释放任何一个Memblock_t对象。