STL空间配置器
来源:互联网 发布:软件开发等级考试 编辑:程序博客网 时间:2024/06/01 08:38
我们为什么要研究STL中的空间配置器呢?
因为STL中的算法,容器等,申请内存不是直接向操作系统要!而是向STL的另一组件空间配置器申请,
STL为什么要这么做呢,假设现在我们要用STL中的list,我们一会插入一个节点,一会插入一个节点,
这些节点的地址在内存中可能不是连续的,假如我们现在要向操作系统申请一大块内存,当我们去申请
的时候操作系统会告诉我们还有足够的内存,但是结果却申请不出来,原因是我们在用list申请空间的时候
在内存中断断续续的没有连接起来,把一整块内存划分了许多小块内存。
这就是内存碎片问题。
SGI对这种问题的设计哲学如下:
1. 像system heap要求空间
2. 考虑多线程状态
3. 考虑内存不足时的应对措施
4. 考虑过多的“小型区块”可能造成的内存碎片(fragment)问题。
考虑到小型区块所可能造成的内存碎片问题,SGI设计了双层级配置器,第一层配置器直接使用
malloc()和free(),第二级配置器则视情况采用不同的策略。当配置区块大于128bytes字节,
则视之为“足够大”,去调用一级空间配置器,如果小于128bytes,则视之为过小,便采用memory
pool 整理方式,而不再求助于第一级配置器,
那么问题来了?整个设计究竟只开放一级配置,还是同时开启二级配置器呢?取决于
__USE_MALLOC是否被定义?(经检测STL中没有定义这个宏)
#if __USE_MALLOC typedef __malloc_alloc_template<0> malloc_alloc; typedef malloc_alloc alloc; //令alloc为一级空间配置器#else //令alloc为二级空间配置器 typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>alloc;#endif
无论alloc被定义为一级空间配置器,或者二级空间配置器,SGI都还会为它在封装一个接口
如下,使配接器的接口能够符合STL接口的标准。
template<class T,class Alloc>class simple_alloc{public: static T* allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::allocate(n*sizeof(T)); } static T* allocate(void) { return 0 == n ? 0 : (T*)Alloc::allocate(sizeof(T)); } static void deallocate(T* p, size_t n) { if (0 != n) { Alloc::deallocate(p, n*sizeof(T)); } } static void deallocate(T*p) { Alloc::deallocate(p, n*sizeof(T)); }};
其内部的四个函数其实都是单纯的转调用,调用传给配置器的(一级或二级)的成员函数。
SGI的容器全部都使用这个simple_alloc接口。
下面我们就先研究一下一级配置器。
一级空间配置器的处理流程:
SGI,以malloc()来配置内存,如果失败就会调用oom_malloc(),如果客户端没有设置
内存不足处理机制,就直接返回std::bad_alloc,如果设置了内存不足处理机制,那么
就会一直循环直到分配到内存为止,如果设计的不好,很容易发生死循环。
注意:设计“内存不足处理机制”是客户端的责任,设定“内存不足处理机制”也是客户端的责任。
typedef void(*MallocAlloc)();template<int Inst> //预留参数class __MallocAllocTemplate{private: static void *_OomMalloc(size_t); //malloc调用失败调用的函数 static MallocAlloc _MallocAllocOomHandler; //函数指针内存不足的时候处理机制public: static void* Allocate(size_t n) { void* result; result = malloc(n); if (n == 0)//如果malloc失败,调用_Oomalloc { _OomMalloc(n); } return result; } static void DeAllocate(void* p) //释放内存 { free(p); } static MallocAlloc _SetMallocHandler(MallocAlloc f) //参数是函数指针,返回值也是函数指针 { MallocAlloc old = _MallocAllocOomHandler; _MallocAllocOomHandler = f; return f; }};template<int Inst>void(*__MallocAllocTemplate<Inst>::_MallocAllocOomHandler)() = 0; //默认不设置内存不足处理机制template<int Inst>void* __MallocAllocTemplate<Inst>::_OomMalloc(size_t n){ MallocAlloc _MyMallocHandler; void* result; while (1) { _MyMallocHandler = _MallocAllocOomHandler; if (0 == _MyMallocHandler) throw std::bad_alloc(); (*_MyMallocHandler)(); //调用内存不足的处理函数 if (result = malloc(n)) //重新申请内存 return result; }}
二级空间配置器的逻辑步骤:
假如现在申请n个字节:
判断n是否大于128,如果大于128则直接调用一级空间配置器。如果不大于,则将n上调至8的倍数处,
然后再去自由链表中相应的结点下面找,如果该结点下面挂有未使用的内存,则摘下来直接返回这块空间的
地址。否则的话我们就要调用refill(size_t n)函数去内存池中申请。向内存池申请的时候可以多申请几个,STL默认一次申请nobjs=20个,将多余的挂在自由链表上,
这样能够提高效率。 进入refill函数后,先调chunk_alloc(size_t n,size_t& nobjs)函数去内存池中申
请,如果申请成功,这时候就有两种情况:- 如果nobjs=1的话则表示内存池只够分配一个,这时候只需要返回这个地址就可以了。
- 如果nobjs大于1,则将多余的内存块挂到自由链表上。
如果chunk_alloc失败的话,在他内部有处理机制。
进入chunk_alloc(size_t n,size_t& nobjs )向内存池申请空间的话有三种情况:
3.1. 内存池剩余的空间足够nobjs*n这么大的空间,则直接分配好返回就可以了。
3.2. 内存池剩余的空间够分配一个size大小,但是不够nobjs*n个,返回剩余大小除以size对象大小
3.3. 内存池中剩余的空间连一个n都不够了,这时候就要向heap申请内存,
不过在申请之前先要将内存池中剩余的内存挂到自由链表上,之后再向heap申请。3.3.1、 如果申请成功的话,则就再调一次chunk_alloc重新分配。
3.3.2、如果不成功的话,这时候再去自由链表中看看有没有比n大的空间,
如果有就将这块空间还给内存池,然后再调一次chunk_alloc重新分配。
3.3.3、如果没有的话,则就调用一级空间配置器分配,看看内存不足处理机制能否处理。
代码如下:
enum { _ALIGN = 8 }; //小区间的上调边界enum { _MAXBYTES=128}; //小型区块的上限enum { _NFREELISTS = _MAXBYTES / _ALIGN }; //free_lists的个数template<bool threads,int Inst>class __DefaultAllocTemplate{private: union _Obj //自由链表节点的类型 { _Obj* _FreeListLink; char _ClientData[1]; };private: static char* _StartFree; //内存池的头指针 static char* _EndFree; //内存池的尾指针 static size_t _HeapSize; //记录内存池已经向heap申请了多大空间 static _Obj* volatile _FreeList[_NFREELISTS]; //自由链表private: static size_t _GetFreeListIndex(size_t bytes) //得到节点的下标 { return (bytes + (size_t)_ALIGN - 1) / (size_t)_ALIGN - 1; } static size_t _GetRoundUp(size_t bytes) //向上取8的倍数 { return (bytes + (size_t)_ALIGN - 1)&(~(size_t)(_ALIGN - 1)); } static void* _ReFill(size_t n); //在自由链表中取 static char* _ChunkAlloc(size_t size, int& nobjs); //在内存池中申请nobjs个对象,每个对象size大小public: static void* Allocate(size_t n); //n大于0 static void DeAllocate(void* p, size_t n); };template<bool threads,int Inst>char* __DefaultAllocTemplate<threads, Inst>::_StartFree = 0;template<bool threads,int Inst>char* __DefaultAllocTemplate<threads, Inst>::_EndFree = 0;template<bool threads,int Inst>size_t __DefaultAllocTemplate<threads, Inst>::_HeapSize = 0;template<bool threads,int Inst>typename __DefaultAllocTemplate<threads, Inst>::_Obj* volatile //前面加typename表示后边是类型__DefaultAllocTemplate<threads, Inst>::_FreeList[_NFREELISTS] = { 0 };template<bool threads,int Inst>void* __DefaultAllocTemplate<threads, Inst>::Allocate(size_t n) //分配空间{ void* ret; if (n > _MAXBYTES) //大于128在一级空间配置器中申请 { ret = malloc_alloc::Allocate(n); } else //在自由链表中找 { _Obj* volatile* MyFreeList = _FreeList + _GetFreeListIndex(n); _Obj* result = *MyFreeList; if (result == NULL) { ret = _ReFill(_GetRoundUp(n)); } else { *MyFreeList = result->_FreeListLink; ret = result; } } return ret;}template<bool threads,int Inst>void __DefaultAllocTemplate<threads, Inst>::DeAllocate(void* p, size_t n)//回收空间{ if (n > _MAXBYTES) { malloc_alloc::DeAllocate(p); } else //将内存回收到自由链表当中 { _Obj* q = (_Obj*)p; _Obj*volatile* MyFreeList = _FreeList + _GetFreeListIndex(n); q->_FreeListLink = *MyFreeList; *MyFreeList = q; }}template<bool threads,int Inst>void* __DefaultAllocTemplate<threads, Inst>::_ReFill(size_t n){ int nobjs = 20; char* chunk = _ChunkAlloc(n, nobjs); if (1 == nobjs) { return chunk; } _Obj* ret = (_Obj*)chunk; //将第一份返回 _Obj* volatile * MyFreeList = _FreeList + _GetFreeListIndex(n); *MyFreeList = (_Obj*)(chunk + n); //将第二个对象的地址放到链表中 _Obj* cur = *MyFreeList; _Obj* next= 0; cur->_FreeListLink = 0; for (int i = 2; i < nobjs; i++) //将剩下的挂在自由链表下 { next = (_Obj*)(chunk + n*i); cur->_FreeListLink = next; cur = next; } cur->_FreeListLink = 0; return ret;}template<bool threads,int Inst>char* __DefaultAllocTemplate<threads, Inst>::_ChunkAlloc(size_t size, int& nobjs) //向系统中申请{ char* result = 0; size_t totalBytes = size*nobjs; //总共要申请的字节数 size_t leftBytes = _EndFree - _StartFree; //内存池中剩余的字节数 if (leftBytes >= totalBytes) //如果剩余的大小大于申请的大小就直接返回 { result = _StartFree; _StartFree += totalBytes; return result; } else if (leftBytes > size) //至少大于一个 { nobjs = (int)(leftBytes / size); result = _StartFree; _StartFree += (nobjs*size); return result; } else //内存池已经不够一个了 { size_t NewBytes = 2 * totalBytes + _GetRoundUp(_HeapSize >> 4); //扩容之前将剩下的就近挂在链表下边 if (leftBytes > 0) { _Obj* volatile * MyFreeList = _FreeList + _GetFreeListIndex(leftBytes); ((_Obj*)_StartFree)->_FreeListLink = *MyFreeList; *MyFreeList = (_Obj*)_StartFree; } //开辟新的内存 _StartFree = (char*)malloc(NewBytes); if (_StartFree == 0) //申请失败 { //如果申请不到,就在自由链表中找,找到了在调用,_ChunkAlloc函数 for (size_t i = size; i < (size_t)_MAXBYTES; i += (size_t)_ALIGN) { _Obj* volatile *MyFreeList = _FreeList + _GetRoundUp(i); _Obj* p = *MyFreeList; if (p != NULL) { _StartFree = (char*)p; //将这块内存拿回到内存池中 *MyFreeList = p->_FreeListLink; _EndFree = _StartFree + i; return _ChunkAlloc(size, nobjs); } } //如果还找不到就去一级空间配置器下去申请,如果还申请不到,那里会抛异常 _EndFree = NULL; _StartFree = (char*)malloc_alloc::Allocate(NewBytes); } //开辟成功,就更新heap_size,更新end_Free; _HeapSize += NewBytes; _EndFree = _StartFree + NewBytes; return _ChunkAlloc(size, nobjs); }}
空间配置器的其他问题:
在空间配置器中所有的函数和变量都是静态的,所以它们在程序结束的时候才会被释放,
二级空间配置器没有将内存还给操作系统,只是把他们都挂在自由链表下,只有当你的程序结束时
才还给操作系统。假如我不断的开辟小块内存,最后将整个heap的内存都挂在自由链表下,但是都没有用这些
空间,再想开辟一块大的空间就会失败。二级空间配置器会出现内碎片问题,假如我一直申请char,那么就会浪费7/8的空间,
但总体来说性能还是比较高的。
- 【STL】STL空间配置器
- STL空间配置器
- STL空间配置器
- STL空间配置器
- 【STL】空间配置器
- STL空间配置器
- STL----空间配置器
- STL-空间配置器
- STL空间配置器
- STL空间配置器
- STL空间配置器
- STL空间配置器
- STL-空间配置器
- STL空间配置器
- STL空间配置器
- 【STL】空间配置器
- STL空间配置器
- STL空间配置器
- HTML5知识点总结
- Pycharm2017专业版安装以及激活
- Redis学习五(Spring Data Redis)
- 你知道python教程哪个好吗?
- dubbo+zookeeper探索小结
- STL空间配置器
- JavaScript 中的单例以及模块模式
- Quartz 教程
- 遇到多个构造器参数时要考虑用构造器。
- 深度学习:神经网络中的前向传播和反向传播算法推导
- fatal: Unable to create '........./.git/index.lock': File exists.
- iOS调试——打全局断点后总是在断在App delegate里处理办法
- 【HDU-1232】 畅通工程
- iOS开发自定义简洁实用的高可扩展的Model基类