STL空间配置器
来源:互联网 发布:面包车拉货软件 编辑:程序博客网 时间:2024/06/05 05:58
STL空间配置器
空间配置器
在STL中,STL的空间配置器是一个名为allocator的模板类,allocator有几个重要个接口,分别为allocator::allocate(),allocator::deallocate(),allocator::construct(),allocator::destroy()。上述所说的allocator只是基层内存配置/释放行为(也就是::operator new和::operator delete)的一层薄薄的包装,并没有考虑到任何效率上的强化,SGI另有法宝。SGISTL在这个项目上脱离了STL标准,使用一个专属的,拥有次层分配能力的、效率优越的特殊配置器。其名称为alloc而非allocator,而且不接受任何参数。所有SGI STL的容器默认的空间配置器为alloc。虽然SGI也定义有一个符合部分标准、名为allocator的配置器,但SGI从未使用过他,因为SGI的allocator只是在::operator new和::operator delete做了一层薄薄的包装而已,效率不佳。以下提到的allocator指STL 空间配置器,不是SGI的allocator。 class foo{ . . . }; foo* pf=new foo; //配置内存,然后构造对象 delete pf; //将对象析构,然后释放内存
上面的new含有两个操作:(1)调用::operator new配置内存;(2)调用foo::foo()进行构造对象内容。delete也含有两个操作:(1)调用foo::~foo()析构对象;(2)调用::operator delete释放内存。为了精密分工,STL的allocator决定将这两个阶段区分开来。内存配置操作由allocator::allocate()负责,内存释放操作由allocator::deallocate(),对对象构造操作由allocator::construct()负责,对对象析构操作由allocator::destroy()负责。
STL的配置器定义于之中,SGI内含有两个文件
#include<stl_alloc.h> #include<stl_construct.h>
构造和析构基本工具:construct()和destroy()
<stl_construct.h>中定义了construt()和destroy()两个函数,用来构造和析构对象。其中construt()使用placemen new运算子来完成。destroy()有两个版本,一个版本直接调用对象的析构函数即可,另一个需要将一个范围的的对象全部析构。但如果在一个范围内析构对象时,析构函数无关痛痒,多次调用析构函数会影响效率,这里destroy()通过_type_traits<>技术来判断应该在循环中对所指范围内的每一个对象调用destroy(),还是什么也不做就结束。以下是construct和destroy的示意图:部分代码如下:#include <new.h> // 欲使用placement new,需先包含此文件template <class T1,class T2>inline void construct(T1 *p,const T2& value){ new (p) T1(value);//调用placement new ;}//以下是destroy()第一个版本,接受一个指针template <class T>inline void destroy(T* pointer) {pointer->~T(); //调用析构函数}// 如果元素的数值型别(value type)有 non-trivial destructor…template <class ForwardIterator>inline void__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) { for ( ; first < last; ++first) destroy(&*first);}// 如果元素的数值型别(value type)有 trivial destructor…template <class ForwardIterator> inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}// 判断元素的数值型别(value type)是否有 trivial destructortemplate <class ForwardIterator, class T>inline void __destroy(ForwardIterator first, ForwardIterator last, T*) { typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; __destroy_aux(first, last, trivial_destructor());}// 以下是 destroy() 第二版本,接受两个迭代器。此函数设法找出元素的数值型别//进而利用__type_traits<>求取最适当措施template <class ForwardIterator>inline void destroy(ForwardIterator first, ForwardIterator last) { __destroy(first, last, value_type(first));}// 以下是destroy() 第二个版本针对迭代器char* 和 wchar_t* 的特化版inline void destroy(char*, char*) {}inline void destroy(wchar_t*, wchar_t*) {}
空间的配置与释放,std::alloc
对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学如下:
- 向system heap要求空间
- 考虑多线程状态
- 考虑内存不足的应变措施
- 考虑过多“小型区块”可能造成的内存碎片问题
为了简化问题,下面的代码排除了多线程状态的处理。
考虑到小型区块所可能造成的内存碎片问题,SGI设计了双层级配置器,第一级配置器直接使用了malloc()和free(),第二级配置器则视情况采用不同策略:当配置区块超过128bytes时,视之为“足够大”
便使用第一级配置器;当配置区块小于128bytes时,视之为“过小”,为降低额外负担,便采用复杂的memory pool整理方式,而不再求助于第一配置器,整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于__USE_MALLOC是否被定义。两个配置器关系如下:
第一级配置器__malloc_alloc_template
第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置,释放,重配置操作,并以实现类似C++ new-handler的机制。 所谓C++new-handler机制是,你可以要求系统在内存配置需求无法被满足时,调用一个你所指的函数。换句话说,一旦::operator new 无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用有客端制定的处理例程。该处理例程通常被称为new-handler。 注意,SGI 以malloc而非::operator new来配置内存,一个原因是历史因素,另一个原因是c++并未提供相应于realloc()的内存配置操作,因此,SGI 不能直接使用C++的new-handler(). 必须仿真一个类似的set_malloc_handler()。 请注意,SGI 第一配置器的allocate()和realloc()都是在调用malloc()和realloc()不成功后,该调用oom_malloc()和oom_realloc(),后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务,但如果“内存不足处理例程”并未被客户端设定,oom_malloc()和oom_realloc()便不老实地调用__THROW_BAD_ALLOC,丢出BAD_ALLOC异常信息,或利用exit(1)硬生中止程序。 部分代码如下: //注意:无“template”型别参数,非型别参数inst完全没有派上用场 template <int inst> class _malloc_alloc_template{ private: //以下三个函数用来处理内存不足情况 static void *oom_malloc(size_t); static void *oom_realloc(void*,size_t); #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG static void (* __malloc_alloc_oom_handler)(); #endif public: static void* allocate(size_t n){ void *result=malloc(n);//直接使用malloc if(result==0)result=oom_malloc(n); return result; } static void deallocate(void *p,size_t){ free(p);//直接使用free } static void* reallocate(void *p,size_t new_size){ void *result=realloc(p,new_size);//第一级配置器直接使用realloc() if(result==0)result=oom_realloc(p,new_size); return result; } //以下类似C++的set_new_handler() static void (* set_malloc_handler(void (*f)()))() { void (*old)()=_malloc_alloc_oom_handler; _malloc_alloc_oom_handler=f; return old; } }; //_malloc_alloc_oom_handler初值为0,给客户定义 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG template <int inst> void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0; #endif template <int inst> void * _malloc_alloc_tempalte<inst>::oom_malloc(size_t n){ void (* my_malloc_handler)(); void *result; for(;;){//不断尝试释放配置 my_malloc_handler=_malloc_alloc_oom_handler; if(my_malloc_handler==0){ __THROW_BAD_ALLOC; } (*my_malloc_handler)();//调用处理例程序 result=malloc(n); //再次尝试内存配置 if(result!=0)return result; } } template <int inst> void * _malloc_alloc_template<inst>::oom_realloc(void *p,size_t n){ void (* my_malloc_handler)(); void *result; for(;;){ my_malloc_handler=_malloc_alloc_oom_handler; if(my_malloc_handler==0){ __THROW_BAD_ALLOC; } (*my_malloc_handler)(); result=realloc(p,n); if(result!=0)return result; } } typedef __malloc_alloc_template<0> malloc_alloc;
第二级配置器 __default_alloc_template
第二级配置器由__default_alloc_template类负责,该配置器做法是:如果区块大于128bytes,就调用第一级配置器。当小于128bytes时,则以内存池管理,每次配置一大块内存,并维护对应的自由链表free-list。下次若再有相同大小的内存需求,就直接从free-lists中拨出。如果客户释放小额区块,就由配置器回收到free-lists中。为了管理方便,配置器会将主动将任何小额区块的内存需求量上调到8的倍数,并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,83,81,88,96,104,112,120,128bytes的小额区块。free-lists节点结构如下: union obj{ union obj* free_list_link; char clinet_data[1]; };
图如下:
第二级配置器部分代码如下:
template <bool threads,int inst> class _default_alloc_tempalte{ private: # ifndef __SUNPRO_CC enum {__ALIGN = 8}; // 小型区块的上调边界 enum {__MAX_BYTES = 128}; // 小型区块的上限 enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // free-lists 个数 # endif //ROUND_UP将bytes上调至8的倍数 static size_t ROUND_UP(size_t bytes){ return (bytes+__ALIGN-1)&~(__ALIGN-1); } union obj{ union obj* free_list_link; char clinet_data[1]; }; # ifdef __SUNPRO_CC static obj * __VOLATILE free_list[]; # else static obj * __VOLATILE free_list[__NFREELISTS]; # endif //根据区块大小,找到决定使用的free_list static size_t FREELIST_INDEX(size_t bytes){ return (bytes+__ALIGN-1)/(__ALIGN-1); } //返回一个大小为n的对象,并可能加入大小为n的其他区块到free_list static void *refill(size_t); //配置一大块空间,可容纳nobjs个大小为"size"的区块 //如果配置nobjs个区块有所不便,nobjs可能会降低 static char *chunk_alloc(size_t size,int &nobjs); static char *start_free;//内存池起始位置,只在chunk_alloc中变化 static char *end_free; //内存池结束为止,只在chunk_alloc中变化 static size_t heap_size; //分配堆的空间大小 public: static void* allocate(size_t n){/*详述与后*/} static void* deallocate(void *p,size_t n){/*详述与后*/} static void* reallocate(void *p,size_t old_size,size_t new_size); }; //下面是static data member的定义于初值设定 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> __default_alloc_template<threads, inst>::obj * __VOLATILE __default_alloc_template<threads, inst> ::free_list[ # ifdef __SUNPRO_CC __NFREELISTS # else __default_alloc_template<threads, inst>::__NFREELISTS # endif ] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
关于allocate函数
static void* allocate(size_t n){ obj * volatile * my_free_list; obj * result; //大于128bytes就调用第一级配置器 if(n>(size_t) _MAX_BYTES) return malloc_alloc::allocate(n); //寻找16个free_lists中合适的一个 my_free_list=free_lists+FREELIST_INDEX(n); result=*my_free_list; if(result==0){ //如果没有找到就调用refill填充free_lists 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){ opj *q=(obj *)p; obj * volatile * my_free_list; //如果大于128就调用第一级配置器 if(n>(size_t) __MAX_BYTES){ malloc_alloc::deallocate(p,n); return; } //寻找对应的free_list my_free_list = free_list+FREELIST_INDEX(n); q->free_list_link=my_free_list; *my_free_list=q; }
如图所示:
重新填充refill函数
代码如下:
template <bool threads,int inst> void* __deault_alloc_template<threads,inst>::refill(size_t n){ int nobjs=20; //调用chunk_alloc(),尝试取得nobjs个区块作为free_list的新节点 //注意参数nobjs是传引用 char *chunk=chunk_alloc(n,nobjs); obj * volatile * my_free_list; obj *result; obj * current,*next_obj; int i; //如果只获得一个区块,这个区块就分配给调用者,free_list无新节点 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){//从1开始,因为第0个将返回给客户端 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; }
chunk_alloc函数 代码:
template <bool threads,int inst> //假设size已经上调至8的倍数 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; //内存池剩余空间 if(bytes_left>=total_bytes){ result=start_free; start_free+=total_bytes; return result; } else if(bytes_left>=size){ //内存池剩余空间不能完全满足需求量,但足够供应1个以上的区块 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(byte_left>0){ //内存池还有一些零头,先分配给适当的free_list //首先寻找适当的free_list obj * volatile * my_free_list=free_list+FREELIST_INDEX(n); //调整free_list,将内存池中的残余空间编入 ((obj*)start_free)->free_list_link=my_free_list; *my_free_list=(obj*)start_free; } //配置heap空间,用来补充内存池 start_free=(char*)malloc(byte_to_get); if(0==start_free){ //heap空间不足,malloc失败 int i; obj * volatile * my_free_list,*p; //试着检视我们手上拥有的东西,这不会造成伤害,我们不打算尝试配置较小的区块, //因为那在多进程中容易造成灾难,以下寻适当的free_list,所谓适当是指“尚有未用区块,且区块足够大”。 for(i=size;i<=__MAX_BYTES;i+=__ALIGN){ my_free_list=free_list+FREELIST_INDEX(i); p=*my_free_list; if(p!=0){//尚有未用区块 //调整free_list已释放出未用区块 *my_free_list=p->free_list_link; start_free=(char *)p; end_free=start_free+i; //递归调整自己,修改nobjs; return chunk_alloc(size,nobjs); //注意,任何残余零头都要编入适当的free_list中备用; } } end_free=0;//如果出现意外(都没有内存用了) //调用第一级配置器,看看out_of_memory机制能否尽力 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); } }
上述的chunk_alloc函数以end_free-start_free来判断内存池的水量,如果充足,就直接调出20个区块返回给free_list,如果水量不足以提供20个区块,但还足够提供至少一个区块,就拨出这不足20个区块的空间出去,这时候其pass by reference 的参数nobjs将修改为实际能提供的区块数,如果内存池连一个区块都无法提供,对客户显然无法交代,此时需要malloc从heap中配置内存,为内存池注入活水,新水量为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
如下图:
阅读全文
0 0
- 【STL】STL空间配置器
- STL空间配置器
- STL空间配置器
- STL空间配置器
- 【STL】空间配置器
- STL空间配置器
- STL----空间配置器
- STL-空间配置器
- STL空间配置器
- STL空间配置器
- STL空间配置器
- STL空间配置器
- STL-空间配置器
- STL空间配置器
- STL空间配置器
- 【STL】空间配置器
- STL空间配置器
- STL空间配置器
- epoll——高并发的功臣
- ubuntu14.04如何在线安装eclipse以及C/C++开发组件,搭建软件开发平台
- 调用python的sklearn实现Logistic Reression算法
- 微信小程序 wx.uploadFile 的编码坑
- 重构--七层登录
- STL空间配置器
- 默认成员函数的几种调用情景
- Python数据分析笔记
- openCV鼠标事件实例
- HotSpot虚拟机中对象的创建
- TCP/IP详解学习笔记(13)-TCP坚持定时器,TCP保活定时器
- 编写一个递归方法,返回数N的二进制表示中1的个数
- STL初步用法
- wordpress获取各类页面链接的函数总结