stl内存池剖析空间配置器

来源:互联网 发布:一知f君2月13号 编辑:程序博客网 时间:2024/06/05 00:27
原代码参考stl库
///*具体只有内存分配代码 类似于c++的newhander 部分代码没有列出*///stl空间配置器的内存池模型#pragma once#include <iostream>#include <malloc.h>#include <vld.h>using namespace std;class _MallocAllocTemplate//定义一级空间配置器申请大内存直接调用这个配置器;对于{public:static void* Allocate(size_t n);static void Deallocate(void *p, size_t size);static void *Reallocate(void *p, size_t old_size, size_t newsize);};void* _MallocAllocTemplate::Allocate(size_t n){void *ret = malloc(n);if (ret == NULL){cout << "malloc fail"<< endl;}return ret;} void _MallocAllocTemplate::Deallocate(void *p,size_t size){free(p);} void* _MallocAllocTemplate::Reallocate(void *p, size_t old_size, size_t newsize) { void *ret = realloc(p,newsize); if (ret == NULL) { cout << "realloc fail" << endl; } return ret; } enum { __ALIGN = 8 }; //小区块的上调边界(8字节对齐) enum { __MAX_BYTES = 128 }; // 小区块的最大值,上限 enum { __NUM_FREE_LIST = __MAX_BYTES / __ALIGN }; //free list个数为128/8=16 8字节对齐所以需要16个指正 template<typename _Ty>//空间配置器   class __DefaultAllocTemplate { private : union Obj { Obj* freeListLink; };//采用这样的结构可以让该指正的next和该指正作为一个使用减少了变量的产生; static char* startFree; static char* endFree; static size_t heapSize; static Obj*  FreeList[__NUM_FREE_LIST]; static size_t RoundUp(size_t bytes)//对所申请的内存进行8字节对齐 { return(bytes + __ALIGN - 1) & ~(__ALIGN - 1); } static size_t FreeListIndex(size_t bytes)//算出大小当前的下标 { return ((bytes + __ALIGN - 1) / __ALIGN) - 1; } static void*ChunAlloc(size_t size, size_t& nobjs) { size_t bytesLeft = endFree - startFree; //内存池剩余空间 size_t totalBytes = size * nobjs; char* ret = NULL; if (bytesLeft >= totalBytes) // 内存池大小足够分配nobjs个对象大小 { ret = startFree; startFree += totalBytes;//需要分配的内存 return ret; } else if (bytesLeft >= size) // 内存池大小不够分配nobjs,但是至少分配一个 { size_t nobjs = bytesLeft / size;//如果不够算出能可以开几个几个 totalBytes = size * nobjs; ret = startFree; startFree += totalBytes; return ret; //返回开辟指正 } else // 内存池一个都分配不了 对第一次进行内存池划分对start和end进行初始haul { //让内存池剩余的那么点挂在freelist上 if (bytesLeft > 0) { size_t index = FreeListIndex(bytesLeft);//算出可挂载该内存的下标中的freelist ((Obj*)startFree)->freeListLink = FreeList[index]; //直接让当前内存块指向该下表的空白内存 FreeList[index] = (Obj*)startFree;//然后让该内存放当前内存的地址; }  size_t bytesToGet = 2 * totalBytes + RoundUp(heapSize>>4);// startFree = (char*)malloc(bytesToGet);//直接malloc开 if (startFree == NULL)//开辟失败就找-  { //申请失败,此时试着在自由链表中找 for (size_t i = size; i <= __MAX_BYTES; i += __ALIGN) //对于存储的每个所对应得下表进行访问查找 { size_t index = FreeListIndex(i);//找出下表具体都是比自己打的 Obj** myFreeList = FreeList + index;//遍历每一条freelist去查找 Obj* p = *myFreeList;//p指向当前链表 if (FreeList[index] != NULL)//判断当前是否还可以插入  { FreeList[index] = p->freeListLink; startFree = (char*)p;//找到当前的点并且将startfree改为当前找到的地址startend改为丁当前的end在调用给他分配内存 endFree = startFree + i; return ChunAlloc(size, nobjs); } } endFree = NULL; //试着调用一级空间配置器 startFree = (char*)_MallocAllocTemplate::Allocate(bytesToGet); } heapSize += bytesToGet; endFree = startFree + bytesToGet; return ChunAlloc(size, nobjs);//再去找 } } static void* Refill(size_t n) { size_t nobjs = 10; char *chunk = (char*)ChunAlloc(n, nobjs); if (nobjs == 1) return chunk; Obj *ret = (Obj*)chunk; Obj* cur = (Obj*)chunk; Obj* next = (Obj*)((char*)cur+n); Obj* volatile *myFreeList = FreeList + FreeListIndex(n);//获取到当前的下标的的freelist链表 *myFreeList = cur;//将当前下标的下一块内存放到当前下标中 for (size_t i=1;i<nobjs;i++)//将申请的每一块内存都链接起来; { cur->freeListLink = next; cur = next; next = (Obj*)((char*)next + n); } cur->freeListLink = NULL; return ret; } public: static void* Allocate(size_t n) { if (n > 128) { return _MallocAllocTemplate::Allocate(n); } Obj* volatile* myFreeList = FreeList + FreeListIndex(n); //定位下标 用二级指针指向这块内存 Obj* ret = *myFreeList; if (ret == NULL) { void* r = Refill(RoundUp(n));//没有可用free list 准备装填 return r; } *myFreeList = ret->freeListLink;//改变一级指正的位置让里面放下一个空闲空间 return ret; } static void Deallocate(void* p, size_t n) { if (n > (size_t)__MAX_BYTES) { _MallocAllocTemplate::Deallocate(p, n); return; } Obj* volatile* myFreeList = FreeList + FreeListIndex(n); Obj* q = (Obj*)p; q->freeListLink = *myFreeList; *myFreeList = q; } }; template <typename T>char* __DefaultAllocTemplate<T> ::startFree = NULL;template<typename T>char* __DefaultAllocTemplate<T>::endFree = NULL;template<typename T>size_t __DefaultAllocTemplate<T>::heapSize = 0;template<typename T>typename __DefaultAllocTemplate<T>::Obj*  __DefaultAllocTemplate<T>::FreeList[__NUM_FREE_LIST];//告诉他是个类型参数template<class _Ty, class Alloc = __DefaultAllocTemplate<_Ty>>class myalloctor{public:typedef _Ty *pointer;typedef const _Ty *const_pointer;typedef _Ty& reference;typedef const _Ty& const_reference;typedef _Ty value_type;void* operator new(size_t size){return Alloc::Allocate(size);}void operator delete(void *p,size_t n){if (p == NULL)return;return Alloc::Deallocate(p,n);}pointer _allocate(int _N, const void *){return (pointer)operator new(_N*sizeof(_Ty)); //给定位置开辟空间返回开辟地点的指针//重载运算符new具体代码在上面  }//构造对象  void construct(pointer _P, const _Ty& _V)//在指定的空间上构造对象  {new (_P)_Ty(_V);}//析构对象  void destroy(pointer _P)//析构对象  {_P->~_Ty();}//释放内存  void deallocate(void *_P, size_t n)//释放内存  {operator delete(_P,n);}};int main(){}

具体结构如下图


1. 内存池有足够大小的空间,则分配申请的空间;
2. 内存池没有足够大小的空间,但是至少还能分配一个节点的空间,则能分多少分多少;
3. 内存池一个节点都腾不出来了,向系统的heap申请2倍于要求大小的空间,在此之间,如果内存池剩余有空间,则放到free-list中去;
4. 如果向heap申请空间失败,那么只能看free-list中更大的节点是否有可用空间了,有则用之,同时递归调用自身修正__nobjs;
5. 如果free-list也没有可用节点了,那么转向第一级空间配置器申请空间;
6. 再不行,第一级空间配置器就要抛出bad_alloc异常了;
问题

1.仔细探究源码之后,你一定会发现一个问题,

  貌似二级空间配置器中的空间重头到尾都没看到他归还给系统。那么问题就是,内存池空间何时释放?

对于这个问题,在回头浏览一下源码及结构图,你就会发现

  大于128的内存,客户程序Deallocate之后会调free释放掉,归还给了系统。

  但是呢...............

  内存池中获取的空间,最终,假定用户都调用Dealloc释放调了,那么他们又在哪里呢?

    没有还给系统,没有在内存池,在自由链表中。

Got it:程序中不曾释放,只是在自由链表中,且配置器的所有方法,成员都是静态的,那么他们就是存放在静态区。释放时机就是程序结束。

2.如果需要释放,那么应该怎么处理呢?

  因为真正可以在程序运行中就归还系统的只有自由链表中的未使用值,但是他们并不一定是连续的(用户申请空间,释放空间顺序的不可控制性),所以想要在合适时间(eg一级配置器的handler中释放,或者设置各阀值,分配空间量到达时处理),就必须保证释放的空间要是连续的。保证连续的方案就是:跟踪分配释放过程,记录节点信心。释放时,仅释放连续的大块。

3.关于STL空间配置器的效率考究

  既然已经存在,而又被广泛使用,那么,整体的效率,以及和STL内部容器之间的使用配合还是没问题的。

我们考虑几种情况:

  a. 用户只需要无限的char类型空间,然而配置器中却对齐到8,于是乎,整个程序中就会有7/8的空间浪费。

  b.对于假定用户申请N次8空间,将系统资源耗到一定程度,然后全部释放了,自由链表中的空间都是连续的。却没有释放。

    但是:用户需要申请大于8的空间时,却依旧没有空间可用。

总结一下,STL对内存的请求与释放


STL考虑到小型内存区块的碎片问题,设计了两级配置器,第一级配置器直接使用malloc和free,第二级配置器根据申请的空间的大小而采用不同的方法,当申请的空间大于128字节时,直接调用调用第一级配置器,当申请的空间小于128字节时,使用一个memory pool的实现机制。 
SGI中默认使用的是第二级空间配置器。 
第二级空间配置器的实现机制:该配置器维护16个free_list,各自管理8,16,24,32,40….128字节的小额区块,当有这样的配置需求时,将申请的空间提升至8的倍数,定位到对应的free_list,直接从free_list取出一块内存,(如果该free_list为空的话,调用refill函数,重新填充free_list,而refill函数调用chunk_alloc函数从内存池中申请空间)当客户端归还内存时,根据归还内存的大小,将内存插入到对应的free_list上。


STL中的内存分配器实际上是基于空闲列表(free list)的分配策略,最主要的特点是通过组织16个空闲列表,对小对象的分配做了优化。 
1)小对象的快速分配和释放。当一次性预先分配好一块固定大小的内存池后,对小于128字节的小块内存分配和释放的操作只是一些基本的指针操作,相比于直接调用malloc/free,开销小。 
2)避免了内存碎片的产生。


释放: 
大于128的内存,客户程序Deallocate之后会调free释放掉,归还给了系统。 
小于128的内存,程序中不曾释放,只是在自由链表中,且配置器的所有方法,成员都是静态的,那么他们就是存放在静态区。释放时机就是程序结束。


原创粉丝点击