STL 二级空间配置器
2016-04-25 18:30
405 查看
当程序需要在堆上申请空间时,我们常用的做法是直接malloc / new一块新的内存空间,这块被申请的空间需要我们手动释放,频繁的申请释放必然会导致系统产生大量内存碎片,尤其是小额区块的申请,内存碎片和额外负担很大的损害了我们的内存空间。
在STL中对于内存的申请,有着更高明的手法——allocator(空间配置器)。
一级空间配置器很简单,只是简单的将malloc进行封装,加入了异常处理机制,申请失败会调用 _oom_malloc_handle,期望通过释放其他不用的空间来重新分配,这样的做法对于内存的管理实际上并没有什么优化。
关键在于二级空间配置器——__default_alloc_template 。
SGI二级配置的做法是,如果申请的区块够大,超过128bytes时,就移交给一级配置器处理,而当区块小于128时,则以内存池(memory pool)管理,此法又称为次层配置 : 每次配置一大块内存,并维护对应的自由链表(free list)。下次如在需要相同大小的内存需求时,就直接free lists中拔出。如果客户释放了小额区块时,就由配置器回收到free lists中。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存扩充至8的倍数(比如申请20bytes,就会调整至24bytes),并维护8,16,24,32,40,48,56,64,72,80,88,94,104,112,128bytes的对应大小的自由链表。
自由链表需要指针进行维护,为了节省开销。我们采用公用体来定义自由链表,如下:
我们先来看看二级空间配置器的具体操作流程:
1 当用户申请内存时,大于128bytes交于第一级配置器直接在内存中malloc空间。小于128交于第二级空间配置器。
2 第二级配置器先根据申请大小,比如30,上升至8的倍数32,并在16条链表中锁定第3条链表,判断链表中是否有32位内存块可供分配(我们第一次申请,没有),那么接着在查看内存池是否还有空间(同样,我们第一次申请,并没有内存池)。
3 那配置器开始向系统申请 32 * 20 * 2 这么大小的内存块,其中20个作为自由链表挂载到第3条链表下(第3条链表对应32位大小的内存),并把第一块内存分配给申请的用户(第3条链现在挂载了19块32位的内存),而剩下的32 * 20大小的空间作为内存池,用head指针指向内存池的头,tail指针指向内存池的尾,记录下内存池。
4 当下一次用户再次申请空间时,比如28bytes,配置器将其上升至32bytes,锁定到第三条链表,查看链表不为空,记录共用体指针,用户写下数据data,记录的指针++,指向下一个32位的内存块。分配成功!
当然,如果申请大小不是24~32位之内的,那么锁定到其他的链表中(比如申请int型,锁定到第0条链表),发现没有对应的内存块,接着查看内存池(32 * 20大小),内存池不为空,判断内存池大小是否大于 8 * 20 ,发现大于,那么就将8*20的大小分配出去,将20个8bytes内存块挂载到第0号自由链表下面,并分配出去一个8bytes给用户,现在剩下19个8bytes大小在0号下面。同时,内存池的head指针指向8*20后,所以内存大小还剩下480bytes。
6 再次申请空间,大小在24~32之间,适配器就将内存池剩下的32bytes分配给用户,内存池为空。当大小大于32位时呢?适配器将先把32bytes挂载到对应链表的内存块下面,也就是2号下面,头插法插入内存块中。然后在向系统malloc空间,比如你需要40bytes,再次malloc 40 * 20 * 2大小的空间,19个挂载到4号链表下面,而剩下40 * 20成为新的内存池大小。
7 当上一个步骤malloc失败了怎么办?也就是说系统现在没有可用的内存供你malloc了,那么会这么做:
就是从4号链表开始查找,链表下的内存块有没有空闲的,4号没有,查找5号48bytes对应的链表,一直查找到7号链表,还有19个64bytes的内存块,拔出一个给用户(虽然用户需要40bytes,我们现在给64bytes有浪费,但也管不了许多了)。
tips: 前面的0号链表下有19*8个内存块,但是我们不能将小的内存块整合分配给40,这样的做法得不偿失。我们只查找 >= 40大小的链表下的内存块。
8 当发现遍历完16个链表任然没有空余的内存块时,这时候二级空间适配器已经黔驴技穷了,只好转交至一级空间适配器,一级适配器调用_oom_malloc_handle,期望通过释放其他的无用内存来重新malloc。当整理内存发现任然没有多余的可供分配时,只能抛出错误,内存分配失败!
下面提供空间配置器的代码实现
二级空间配置器代码及注释参考 《STL源码剖析》。
在STL中对于内存的申请,有着更高明的手法——allocator(空间配置器)。
一级空间配置器很简单,只是简单的将malloc进行封装,加入了异常处理机制,申请失败会调用 _oom_malloc_handle,期望通过释放其他不用的空间来重新分配,这样的做法对于内存的管理实际上并没有什么优化。
关键在于二级空间配置器——__default_alloc_template 。
SGI二级配置的做法是,如果申请的区块够大,超过128bytes时,就移交给一级配置器处理,而当区块小于128时,则以内存池(memory pool)管理,此法又称为次层配置 : 每次配置一大块内存,并维护对应的自由链表(free list)。下次如在需要相同大小的内存需求时,就直接free lists中拔出。如果客户释放了小额区块时,就由配置器回收到free lists中。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存扩充至8的倍数(比如申请20bytes,就会调整至24bytes),并维护8,16,24,32,40,48,56,64,72,80,88,94,104,112,128bytes的对应大小的自由链表。
自由链表需要指针进行维护,为了节省开销。我们采用公用体来定义自由链表,如下:
union obj { union obj * free_list_link; char client_data[1]; };
我们先来看看二级空间配置器的具体操作流程:
1 当用户申请内存时,大于128bytes交于第一级配置器直接在内存中malloc空间。小于128交于第二级空间配置器。
2 第二级配置器先根据申请大小,比如30,上升至8的倍数32,并在16条链表中锁定第3条链表,判断链表中是否有32位内存块可供分配(我们第一次申请,没有),那么接着在查看内存池是否还有空间(同样,我们第一次申请,并没有内存池)。
3 那配置器开始向系统申请 32 * 20 * 2 这么大小的内存块,其中20个作为自由链表挂载到第3条链表下(第3条链表对应32位大小的内存),并把第一块内存分配给申请的用户(第3条链现在挂载了19块32位的内存),而剩下的32 * 20大小的空间作为内存池,用head指针指向内存池的头,tail指针指向内存池的尾,记录下内存池。
4 当下一次用户再次申请空间时,比如28bytes,配置器将其上升至32bytes,锁定到第三条链表,查看链表不为空,记录共用体指针,用户写下数据data,记录的指针++,指向下一个32位的内存块。分配成功!
当然,如果申请大小不是24~32位之内的,那么锁定到其他的链表中(比如申请int型,锁定到第0条链表),发现没有对应的内存块,接着查看内存池(32 * 20大小),内存池不为空,判断内存池大小是否大于 8 * 20 ,发现大于,那么就将8*20的大小分配出去,将20个8bytes内存块挂载到第0号自由链表下面,并分配出去一个8bytes给用户,现在剩下19个8bytes大小在0号下面。同时,内存池的head指针指向8*20后,所以内存大小还剩下480bytes。
6 再次申请空间,大小在24~32之间,适配器就将内存池剩下的32bytes分配给用户,内存池为空。当大小大于32位时呢?适配器将先把32bytes挂载到对应链表的内存块下面,也就是2号下面,头插法插入内存块中。然后在向系统malloc空间,比如你需要40bytes,再次malloc 40 * 20 * 2大小的空间,19个挂载到4号链表下面,而剩下40 * 20成为新的内存池大小。
7 当上一个步骤malloc失败了怎么办?也就是说系统现在没有可用的内存供你malloc了,那么会这么做:
for(i = size; i <= _MAX_BYTES; i += _ALIGN)
就是从4号链表开始查找,链表下的内存块有没有空闲的,4号没有,查找5号48bytes对应的链表,一直查找到7号链表,还有19个64bytes的内存块,拔出一个给用户(虽然用户需要40bytes,我们现在给64bytes有浪费,但也管不了许多了)。
tips: 前面的0号链表下有19*8个内存块,但是我们不能将小的内存块整合分配给40,这样的做法得不偿失。我们只查找 >= 40大小的链表下的内存块。
8 当发现遍历完16个链表任然没有空余的内存块时,这时候二级空间适配器已经黔驴技穷了,只好转交至一级空间适配器,一级适配器调用_oom_malloc_handle,期望通过释放其他的无用内存来重新malloc。当整理内存发现任然没有多余的可供分配时,只能抛出错误,内存分配失败!
下面提供空间配置器的代码实现
enum {_ALIGN = 8}; enum {_MAX_BYTES = 128}; enum {_NFREELISTS = _MAX_BYTES / _ALIGN}; //声明变量及初始化 template<bool threads, int inst> char *__default_alloc_template<threads, inst>::start_free = 0; template<bool threads, int inst> char *__default_alloc_template<threads, inst>::end_free = 0; template<bool threads, int inst> size_t __default_alloc_template<threads, inst>::heap_size = 0; template<bool threads, int inst> typename __default_alloc_template<threads, inst>::obj* volatile __default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //**配置器的运行流程** template<bool thread, int inst> class_default_alloc_template { private: static size_t ROUND_UP(size_t bytes) //ROUND_UP 上升至8的倍数 { return(bytes + _ALIGN-1)&~(—ALIGN-1)); //确定在哪个链表 } private: union obj { union obj *free_list_link; char client_data[1]; }; private: static obj *volatile free_list[_NFREELISTS]; static size_t FREELIST_INDEX(size_t bytes) { //判断在哪个链表 return((bytes + _ALIGN - 1)/_ALIGN -1); } static char *start_free; //内存池的起始位置 static char *end_free; //内存池的终止位置 static size_t heap_size; private: static void *refill(size_tn); static char *chunk_alloc(size_t size, int &nobjs); public: //**空间配置函数allocate()** static void* allocate(size_t n) { obj * volatile *my_free_list; obj * result; //大于128调用1级配置器 if(n > __MAX_BYTES) { return malloc_alloc::allocate(n); } //寻找到16个链表的适当一个 my_free_list = free_list + FREELIST_INDEX(n); //没有找到可用的链表,就准备填充链表 if(result == 0) { void *r = refill(ROUND_UP(n)); return r; } //调整链表 *my_free_list = result->free_list_link; return result; } //空间释放函数deallocate() static void deallocate(void *p,size_t n) { obj *q = (obj *)p; obj * volatile * my_free_list; //大于128就调用一级配置器 if(n > __MAX_BYTES) { malloc_alloc::deallocate(p,n); return; } //寻找对应的free list my_free_list = free_list + FREELIST_INDEX(n); //调整free_list,回收区块 q->free_list_link = *my_free_list; *my_free_list = q; } static void* reallocate(void *p, size_t old_sz, size_t new_sz); }; //**重新填充函数 free lists** template<bool threads, int inst> void* __default_alloc_template<threads,inst>::refill(size_t n) { int nobjs = 20; //调用chunk_alloc(),尝试取得nobjs个区块作为free list 的新节点。 char *chunk = chunk_alloc(n, nobjs); obj *volatile * my_free_list; obj * result; obj * current_obj, *next_obj; int i; //如果只获得一个区块,这个区块就分配给调用者用。 if(1 == nobjs) return chunk; //否则准备调整,free list, 纳入新节点 my_free_list = free_list + FREELIST_INDEX(n); //在chunk空间建立 free list result = (obj*)chunk; //引导free list 指向新的配置空间 *my_free_list = next_obj = (obj*)(chunk+n); //将 free list 的各个节点串联起来 for(i=1; ;++i) { current_obj = next_obj; next_obj = (obj*)((char*)next_obj+n); if(nobjs - 1 == i) { current_obj->free_list_link = 0; break; } else { current_obj->free_list_link = next_obj; } } return result; } //**内存池 memory pool** template<bool threads, int inst> char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int &nobjs) { char *result; size_t total_bytes = size * nobjs; size_t bytes_left = end_free - start_free; //内存池的空间大于 size * 20 if(bytes_left >= total_bytes) { result = start_free; start_free += total_bytes; return result; } //如果不能满足20的内存块,够不够1个? else if(bytes_left >= size) { nobjs = bytes_left / size; total_bytes = size * nobjs; result = start_free; start_free += total_bytes; return result; } else { //连一个都不够 size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4); if(bytes_left > 0) { //将内存池残留内存挂载到某个链表下面 obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left); ((obj*)start_free)->free_list_link = *my_free_list; *my_free_list = (obj*)start_free; } //向系统重新malloc申请 size * 20 * 2大小内存 start_free = (char *)malloc(bytes_to_get); if(0 == start_free) //当系统内存不足,无法malloc { int i; obj * volatile * my_free_list, *p; //从size开始,检查之后链表下面是否还有空闲内存块,分配给用户。 for(i = size; i<=__MAX_BYTES; i+=__ALIGN) { my_free_list = free_list + FREELIST_INDEX(i); p = *my_free_list; if(0 != p) { //有未用内存块 *my_free_list = p->free_list_link; start_free = (char *)p; end_free = start_free + i; //递归调用自己,修正nobjs return chunk_alloc(size,nobjs); } } end_free = 0; //没有闲余内存块,只能调用一级空间适配器 start_free = (char *)malloc_alloc::allocate(bytes_to_get); } heap_size += bytes_to_get; end_free = start_free + bytes_to_get; //递归调用自己,修正nobjs return chunk_alloc(size,nobjs); } }
二级空间配置器代码及注释参考 《STL源码剖析》。
相关文章推荐
- 机器学习之——多类分类问题
- 七种排序算法的JAVA实现
- 51Nod-1088-最长回文子串
- GitHub上史上最全的Android开源项目分类汇总
- 地理编码
- Python与shell的3种交互方式介绍
- 定时器显示礼物弹出的动画,不错的思路
- 命令行创建AVD
- 数组初始化小工具
- [BZOJ1597][Usaco2008 Mar]土地购买(斜率优化dp)
- mysql相似于oracle的to_char() to_date()方法
- 模拟大数据的基本计算, 以解决常规计算器计算数据时位数的有限性
- UVA10723 Cyborg genes (LCS)
- UVa 101 - The Blocks Problem
- java显示声音波形图示例
- 123
- 最小生成树(prime算法、kruskal算法) 和 最短路径算法(floyd、dijkstra)
- 链表中环的入口结点
- linux jps: command not found
- [设计模式学以致用]备忘录模式