一种高性能小区块内存管理方法
技术领域
本发明涉及一种高性能小区块内存管理方法。
背景技术
内存管理是计算机中为有效利用程序运行的重要资源-内存而设计的一套有效管理该资源的计算机算法,因此内存管理技术是计算机领域中应用最广泛的基础技术之一。从操作系统的段页式内存管理,到CRT malloc的块式内存管理,对算法时间和空间效率的追求都无不用其极。几乎每个优秀的C/C++库都自带自己的内存管理模块,实现一个万能的内存管理算法一直是业界难题。常见的内存管理库有:
dlmalloc,Doug Lea从1987年开始编写;
ptmalloc,GLibc在dlmalloc基础上增加多线程优化;
TCMalloc,Google开源库google-perftools成员;
Apache基于连接的内存池;
boost内存池;
SGI STL allocator;
Loki small object allocator。
下面分别描述其中两种具有代表性的现有技术方案的实现细节。
SGI STL allocator是由Silicon Graphics [Computer System] Inc实现的一个C++标准模板库(STL)版本中实现的内存分配器。如图1所示,SGI STL allocator小区块内存管理部分以8的整数倍为界维护16个自由链表,分别负责小于等于128字节的各个区块的内存分配请求。自由链表空间不足以分配时,会从备用池pool中补充一定数量block到自由链表。当备用池pool不够填充自由链表的时候,会调用malloc向操作系统请求更大的备用池以备后用。
回收时,只需要将区块挂接到对应自由链表头部就可以了。可见该算法的小区块分配和释放的效率相当高。
Loki small object allocator是一个著名的研究C++设计模式的开源算法库Loki中的小对象内存分配器的实现。如图2所示,Loki small object allocator维护若干个SmallObjAllocator,分别负责特定大小的区块分配回收请求。
每个SmallObjAllocator内部由若干Chunk列表组成,Chunk的个数是会在运行过程中动态增减。Chunk是Loki向操作系统申请和释放的大块内存单元,内部通过指针和数组下标的方式管理若干个等大小的block备用。
Loki的巧妙之处在于,通过数组下标的形式,将Chunk内的空闲区块串联起来。向Chunk请求区块时,如果blocksAvailable_大于0,则将firstAvailableBlock_所示的block内容回写到firstAvailableBlock_,blocksAvailable_减1,并返回该block即可。
回收区块时,同样只需将firstAvailableBlock_内容回写该block,该block所在数组下标index回写firstAvailableBlock_,blocksAvailable_加1即可。此时如果blocksAvailable_达到最大区块数,可将Chunk释放给操作系统。
可见Loki在Chunk内的内存管理方法设计是相当巧妙的,效率也相当高,并且可以在合适的时候将Chunk回收给操作系统。
以上描述了SGI STL allocator和Loki small object allocator的核心设计思想和主要优点。但是两种设计精良的内存管理算法仍然各有不足:
SGI STL allocator从设计上无法实现向操作系统回收内存,并且free list之间也会出现用较大free list的block切分成更小的block来满足其他free list空间请求的情况,反之却不能实现更小的block合并成更大的block来使用。
因此,SGI STL allocator可能出现实际掌握大量空闲小区块,却仍然无法满足新的内存请求的情况,甚至在一些极端应用场景下,这种内存碎片问题,足以吞噬整个操作系统内存,造成所有进程都无内存可用的危险场景。
Loki small object allocator在Chunk内部的设计相当精巧高效。但由于Chunk本身在SmallObjAllocator中是以vector形式管理的,在allocator分配区块时需要查找可用Chunk,释放区块需要查询block需要释放给哪个Chunk的时候,却不得不使用低效笨拙的变相线性查找方法(虽然作者在实现手法上已有优化)。
由于SmallObjAllocator中Chunk的实际数量是动态变化实际数量可能会很大,因此,在一些极端应用场景下,Loki small object allocator的实际内存分配和释放效率是不容乐观的。
发明内容
针对以上现有技术的缺陷,本发明的目的是,保留以上两种优秀内存管理算法的优点的同时改进其设计上的明显缺陷,设计出一个高效灵活,即使在极端应用场景下也能有优秀性能表现的小区块内存管理算法。因为本算法设计原理更接近Loki,又从SGI STL得到灵感,因此得名为逻技池(Logipool)。
本发明的技术方案为:
一种高性能小区块内存管理方法,预设最大字节数,将小于等于预设最大字节数的内存操作请求纳入小区块管理的范围,进而通过对应的固定池(fix pool)代理完成小区块范围内特定大小的内存操作请求;
预设最大自由链表长度值,在由空闲的可用区块组成的自由链表(free_list)的长度小于预设的最大自由链表长度值时执行浅回收,否则执行深回收,其中浅回收为向采用本方法的内存管理模块快速回收使用完毕的内存区块,而深回收则在合适的时候将区块回收归还给操作系统。
作为以上技术的一种补充,每个fix pool负责特定大小的内存操作请求并构造:
page_list, 由当前fix pool管理的所有向操作系统申请和释放的大块内存单元(page)组成的双向循环链表,由可实现快速移动节点,快速在头部和尾部执行插入操作的数据结构(如双向循环链表)组成;
page_set,由page_list中所有page首地址组成的集合,用于快速查找区块所属page,由可以实现快速查找的数据结构(如AVL树、红黑树)组成;
free_list,由空闲的可用区块组成的自由链表,浅回收模式下实现快速分配和回收区块的载体,由可实现在头部快速插入和删除操作的数据结构(如单向链表)组成。
作为以上技术的一种补充,所述方法的内存回收含有以下步骤:
1)如果free_list长度小于最大自由链表值,则执行浅回收,将区块插入到free_list头部并返回;否则转到步骤2);
2)执行深回收,从page_set快速查找区块所属page,并尝试向该page回收区块,成功则将该page移动到page_list头部,以上任何条件不满足则断言非法区块,终止本次请求;
作为以上技术的一种补充,所述方法包括强制深回收行为,其取出free_list中所有区块,并对每个区块,从page_set快速查找区块所属page,并尝试向该page回收区块,成功则将该page移动到page_list首部,以上任何条件不满足则断言非法区块,终止本次请求。
作为以上技术的一种补充,所述方法的内存分配含有以下步骤:
1)如果free_list不为空,则取出free_list首部区块返回,否则转到步骤2);
2)如果page_list为空或者首部单元(head page)没有区块可分配则转到步骤3),否则从该page取出一个区块,如果分配后该page已经没有区块可分配,则将该内存单元移动到page_list尾部,返回已取出的区块;
3)向操作系统申请一个page并初始化放到page_list首部,如果成功转到步骤2),失败则转到步骤4);
4)执行强制深回收后,重新申请page,如果成功转到步骤2),否则分配失败,返回结果。
由上可见,本发明的一些具代表性的特征在于:
1)负责特定大小区块请求的fix pool由page_list,page_set,free_list组成。
2)page_list由可实现快速移动节点,快速在头部和尾部执行插入操作的数据结构组成,如采用双向循环链表。
3)page_set由可以实现快速查找的数据结构组成,如采用AVL树或红黑树。
4)free_list由可实现在头部快速插入和删除操作的数据结构组成,如采用如单向链表。
5)page管理数据部分和page管理的实数据block数组在连续空间上。
6)设置合适的最大自由链表长度(MAX_FREE_LIST)值,回收区块时,free_list长度小于MAX_FREE_LIST时执行浅回收,否则执行深回收。
7)最近一次有区块回收的page移动到page_list首部,最近一次分配完所有区块的page移动到page_list尾部。
因此,本发明的有益效果在于:
1)浅回收方式下,内存分配回收请求只在free_list头部做删除和插入操作,速度可与SGI STL allocator相媲美,达到O(1)复杂度,比Loki small object allocator快很多。
2)向page_list请求分配区块,只需要在head page上进行操作,与Loki small object allocator变相线性查找可用Chunk的低效操作相比,不存在效率瓶颈。
3)向page_list回收区块时,查找block所属page,只需要在page_set上执行一次lower_bound操作,效率达到O(Log(n)),与Loki small object allocator的变相线性查找相比,效率大大改善,不存在效率瓶颈。
4)深回收时,page有机会在所有的block都已回收时释放给操作系统,没有SGI STL allocator的无法向操作系统回收内存,allocator占有内存只增不减的问题。
5)page内部管理大小相等的若干block,不再进一步做block切分,没有SGI STL allocator的大区块可能切分成小区块,小区块却无法合并成大区块的操作,因此不会产生内部内存碎片,不会出现SGI STL allocator中allocator实际掌握大量空闲区块,却仍然无法满足新的内存分配请求的危险。
6)只需要简单调整MAX_FREE_LIST的数值,就可以灵活地定制出在时间性能和空间性能上有较好平衡,适用于特定应用场景的分配器。
7)既没有SGI STL allocator无法向操作系统回收内存以及大量内部内存碎片问题,也没有Loki small object allocator极端状况下线性查找带来的明显效率瓶颈。
总体上讲,本发明所提出的Logipool内存管理方法综合性能表现优秀且平稳,内存管理的空间代价低。
附图说明
以下结合附图和实施例对本发明作进一步说明,其中:
图1为SGI STL allocator小区块内存管理的原理示意图;
图2为Loki small object allocator内存管理的原理示意图;
图3为本发明所提出的Logipool的原理示意图;
图4为本发明中内存分配的流程图;
图5为本发明中内存回收的流程图。
具体实施方式
本发明公开一种高性能小区块内存管理算法-逻技池(Logipool),其基于两个现有的小区块内存管理算法SGI STL allocator和Loki small object allocator对内存管理进行改进。
首先,以下是对本文所涉及的一些概念的解释:
fix pool,如图3 中的pool#0~pool#31所示,固定池,负责代理完成特定大小内存操作请求;
page,如图3 中的page#1~page#N所示,地位等同于Loki中的Chunk,是Logipool向操作系统申请和释放的大块内存单元,大小一般小于并接近4096 bytes;
block,如图3 中blk#1~#N和page的data数组元素所示,是fix pool管理和向客户分配的数据块表示形式,在同一个fix pool内是等大小的;
浅回收,向内存管理模块快速回收使用完毕的内存块,以便下次可以快速分配出去。在本文中即向fix pool的free_list快速回收内存块,内存块所有权实际被Logipool占有;
深回收,向操作系统回收内存块,以便其他进程可以更好的使用内存资源。在本文中即向fix pool的page_list回收区块,page如果全回收会释放给操作系统。
如图3所示, 本发明所提出的Logipool将小于等于预设最大字节数(MAX_BYTES)的内存操作请求纳入小区块管理的范围,大于MAX_BYTES的内存操作请求直接调用malloc函数满足。
Logipool管理fix pool数组,由每个fix pool代理完成特定大小的内存操作请求。如以ALIGN_BYTES的整数倍为区间维护MAX_BYTES/ALIGN_BYTES个fix pool,每个fix pool负责指定大小的内存操作请求。
在具体实施中设置MAX_BYTES = 256,ALIGN_BYTES = 8,实际维护32个fix pool(图3中的 pool#0~pool#31,分别负责8,16...256 bytes的内存操作请求)。对Logipool的内存操作请求,都分别转由相应的fix fool代理完成。
每个fix pool维护3个主要数据结构:page_list, 由当前fix pool管理的所有page组成的双向循环链表;page_set, page_list中所有page首地址组成的集合,可由std::set代理,用于快速查找page;free_list,地位等同于SGI STL中的free_list,浅回收模式下实现快速分配和回收区块的载体。
由于Logipool所有的小区块分配和回收请求都是转由特定的fix pool代理完成的,所以以下算法实现中只讨论fix pool的各项行为,不再讨论Logipool的行为。
一般供容器使用的内存管理算法只需要实现allocate和deallocate行为,Logipool由于free_list的引入,存在深回收和浅回收概念,需要额外提供一个clean行为,实现将所有浅回收给Logipool的空闲区块深回收,归还给操作系统。下面分别介绍各个接口实现细节。
首先对于内存分配allocate,如图4所示:
1)如果free_list不为空,则取出free_list头部block返回,否则转到2);
2)如果page_list为空或者head page没有block可分配则转到3),有则从该page取出一个block,如果分配后该page已经没有block可分配,则将page移动到page_list尾部,返回已取出block;
3)向操作系统申请一个page并初始化放到page_list首部,如果成功转到2),失败则转到4);
4)执行清空(clean)深回收后,并重新申请page,如果成功转到2),否则分配失败,返回0。
5)从page中分配block的过程跟Loki从Chunk分配block过程一样,简单描述就是如果free_blk_cnt大于0,则将fst_blk_idx指向的区块内容填到fst_blk_idx,并将free_blk_cnt减1,返回之前fst_blk_idx指向的区块。
其次,对于内存回收deallocate,如图5所示:
1)如果free_list长度小于MAX_FREE_LIST,则执行浅回收,即将block插入到free_list头部并返回;否则转到2);
2)执行深回收:如果lo=page_set.lower_bound(block)不为page_set.begin(),则尝试向lo-1所在的page回收block,成功则将该page移动到page_list头部,以上任何条件不满足则断言非法区块,终止算法;
3)向page回收block的过程跟Loki向Chunk回收区块过程一样,简单描述就是计算出block所在的index,并将fst_blk_idx的内容写到block上,fst_blk_idx改写为index,free_blk_cnt加1。如果page已经是全回收状态,则向操作系统回收该page。
再次,本发明还包括额外的clean行为,取出free_list中所有block,并依次调用先前所述的内存深回收(即内存回收中的步骤2)过程。
综上所述,本发明的一些具有代表性的特征在于:
1)负责特定大小区块请求的fix pool由page_list,page_set,free_list组成。
2)page_list由可实现快速移动节点,快速在头部和尾部执行插入操作的数据结构组成,如双向循环链表。
3)page_set由可以实现快速查找的数据结构组成,如AVL树、红黑树。
4)free_list由可实现在头部快速插入和删除操作的数据结构组成,如单向链表。
5)page管理数据部分和page管理的实数据block数组在连续空间上。
6)设置合适的最大自由链表长度(MAX_FREE_LIST)值,回收区块时,free_list长度小于MAX_FREE_LIST时执行浅回收,否则执行深回收。
7)最近一次有区块回收的page移动到page_list首部,最近一次分配完所有区块的page移动到page_list尾部。
除此之外,page_set可以由std::set代理,也可以自己实现一个查询效率不低于std::set,底层空间需求可控,且针对该应用场景优化的数据结构代替(如AVL树)。
另外,MAX_FREE_LIST也可以按每个fix pool分别设计。针对特定大小和数量的区块请求的时间空间优化,可以不影响其他fix pool的时间空间性能平衡。
由上可见,本发明的好处在于:
浅回收方式下,内存分配回收请求只在free_list头部做删除和插入操作,速度可与SGI STL allocator相媲美,达到O(1)复杂度,比Loki small object allocator快很多。
向page_list请求分配区块,只需要在head page上进行操作,与Loki small object allocator变相线性查找可用Chunk的低效操作相比,不存在效率瓶颈。
向page_list回收区块时,查找block所属page,只需要在page_set上执行一次lower_bound操作,效率达到O(Log(n)),与Loki small object allocator的变相线性查找相比,效率大大改善,不存在效率瓶颈。
深回收时,page有机会在本page管理的所有的block都已回收时释放给操作系统,没有SGI STL allocator的内存无法向操作系统回收,allocator占有内存只增不减的问题。
page内部管理大小相等的若干block,不再进一步做block切分,没有SGI STL allocator的大区块可能切分成小区块,小区块却无法合并成大区块的操作,因此不会产生内部内存碎片,不会出现SGI STL allocator中allocator实际掌握大量空闲区块,却仍然无法满足新的内存分配请求的危险。
只需要简单调整MAX_FREE_LIST的数值,就可以灵活地定制出在时间性能和空间性能上有较好平衡,适用于特定应用场景的分配器。
既没有SGI STL allocator无法向操作系统回收内存以及大量内部内存碎片问题,也没有Loki small object allocator极端状况下线性查找带来的明显效率瓶颈。综合性能表现优秀且平稳,内存管理的空间代价低。
本发明除了上述实施方式之外,其等同技术方案也应当在其保护范围之内,在此不再一一叙述。