【STL】空间配置器
2017-04-01 21:46
471 查看
本篇博文旨在介绍STL六大组件之一——空间配置器的概念;介绍了内存池的概念;介绍了空间配置器的优缺点;最后实现了一个简单的空间配置器
通常我们习惯直接使用new、malloc等API申请分配内存
然而这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
调用函数会压栈出栈,这些都是有开销的
这就会造成,即使是有足够的内存(40字节),但是通过申请并不能得到一个连续24字节的内存
从而引入了空间配置器
若是,调用一级空间配置器
否则,调用二级空间配置器
而二级空间配置器则是一串自由链表,自由链表中存着不同大小的空间节点;开始从内存中申请空间,并放入自由链表中,由自由链表统一进行管理
一级空间配置器封装了malloc 和 free
并提供给用户可以自行设置的句柄函数
来管理内存的释放
二级空间配置器是一串自由链表,通过内存池和自由链表共同管理内存
2、提高了用户使用内存的效率
也就是说,空间适配器虽然解决了外碎片,但是引入了内碎片
2、空间配置器没有释放内存的机制,当用户频繁使用内存,后来使用完了归还时,仅仅只是归还给了空间配置器
而空间配置器并没有归还给操作系统,直到程序结束的时候才会归还
这就一定程度上导致了内存不用却一直占着的问题
内存池
内存池是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请分配内存
然而这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
STL为什么需要空间适配器
原因1、频繁的申请和释放空间,会降低运行的效率
频繁的申请和释放,就会频繁的调用malloc函数和free函数调用函数会压栈出栈,这些都是有开销的
原因2、频繁的申请和释放空间,会造成内存碎片的问题
这就会造成,即使是有足够的内存(40字节),但是通过申请并不能得到一个连续24字节的内存
从而引入了空间配置器
空间适配器
概念
空间适配器是STL,标准模板库的六大组件之一,其余分别是:容器,迭代器,算法,配接器,仿函数实现方法
申请的字节数是否大于128个字节?若是,调用一级空间配置器
否则,调用二级空间配置器
实现的原理
一级空间配置器封装了malloc,free函数,还有个可以用户自己传入的set_hander设置句柄机制(用来释放内存)而二级空间配置器则是一串自由链表,自由链表中存着不同大小的空间节点;开始从内存中申请空间,并放入自由链表中,由自由链表统一进行管理
可以配置的特性
用户可以通过定义或取消 __USE_MALLOC 宏来选择是否使用二级空间配置器二级空间配置器的原理
模拟实现
一级空间配置器
//定义一级空间配置器 template <int inst> class __MallocAllocTemplate { private: //进行空间的申请,调用Malloc static void *OOM_Malloc(size_t); //调用Realloc static void *OOM_Realloc(void *, size_t); //自己可以设置的句柄函数指针 static void(*__MalloAlloc_OOM_Hander)(); public: //进行空间的申请 static void* Allocate(size_t n) { //直接调用malloc,若不成功,调用OOM_Malloc void* ret = malloc(n); if (ret == NULL) ret = OOM_Malloc(n); return ret; } //进行空间的释放 static void Deallocate(void* p) { //直接调用free() free(p); } //进行空间的重新分配 static void* Reallocate(void* p,size_t newsize) { void* ret = realloc(p, newsize); if (ret == NULL) ret = OOM_Realloc(p, newsize); return ret; } //设置申请空间的句柄 static void(*SetMallocHander(void* fun()))() { //返回的是改变之前的函数指针 void(*old)() = SetMallocHander; SetMallocHander = f; return old; } }; //定义为纯虚函数 //自己可以传入自己定义的函数 template<int inst> void(*__MallocAllocTemplate<inst>::__MalloAlloc_OOM_Hander)() = 0; //void(*__MallocAllocTemplate<inst>::SetMallocHander)() = 0; template<int inst> void* __MallocAllocTemplate<inst>::OOM_Malloc(size_t n) { //定义函数指针 void(*my_malloc_realloc_hander)(); //定义返回值 void* ret; while (1) { //注意:如果自己定义的__MallocAlloc_OOM_Hander无法整理空间 //则会导致死循环 my_malloc_realloc_hander = __MalloAlloc_OOM_Hander; if (my_malloc_realloc_hander == 0) throw("__THROW_BAD_ALLOC"); //通过函数指针调用 (*my_malloc_realloc_hander)(); //进行申请,若申请成功,则返回 //否则,进行循环,重新整理碎片,再申请空间 ret = malloc(n); if (ret) return ret; } } //和OOM_Malloc()原理是一样的 template<int inst> void* __MallocAllocTemplate<inst>::OOM_Realloc(void* p, size_t n) { void(*my_malloc_realloc_hander); void* ret; while (1) { my_malloc_realloc_hander = __MalloAlloc_OOM_Hander; if (my_malloc_realloc_hander == 0) throw("__THROW_BAD_ALLOC"); (*my_malloc_realloc_hander)(); ret = realloc(p, newsize); if (ret) return ret; } }
一级空间配置器封装了malloc 和 free
并提供给用户可以自行设置的句柄函数
来管理内存的释放
二级空间配置器
//定义二级空间配置器 template <bool threads, int inst> class __DefaultAllocTemplate { public: //定义整形常量 static const int __ALIGN = 8;//排列基准值 static const int __MAX_BYTES = 128;//最大值 static const int __NFREELISTS = __MAX_BYTES / __ALIGN;//自由链表大小 static char* startFree;//内存池水位线的开始 static char* endFree;//内存池水位线的结束[ static size_t heapSize;//从系统分配堆的总大小 //向上对齐 //自动对齐到8个字节 static size_t ROUND_UP(size_t size) { return (((size)+__ALIGN - 1) & ~(__ALIGN - 1)); } public: union obj { //指向下一个节点的指针 union obj * freeListLink; /* 这条语句可暂不理解 */ char client_data[1]; /* 客户要看见他?? */ }; //定义自由链表 static obj * freeList[__NFREELISTS]; //求出自由链表对应的下标 //size > 0 static size_t FREELIST_INDEX(size_t size) { return ((size- 1) / __ALIGN); } //从内存池获得大块内存并链到自由链表中 static void *Refill(size_t size); //从内存池中,获得内存,nobjs由于传的引用,代表申请成功的空间个数 static char *chunkAlloc(size_t size, int &nobjs); public: static void* Allocate(size_t n) { __TRACE_DEBUG("请求内存:%d\n", n); obj** myFreeList; obj* ret; //大于128个字节,调用一级空间配置器 if (n >= __MAX_BYTES) return __MallocAllocTemplate<0>::Allocate(n); size_t index = FREELIST_INDEX(n); myFreeList = freeList + index; ret = *myFreeList; if (ret == NULL) { //如果当前位置没有挂节点,就调用Refill进行申请 void * r = Refill(n); __TRACE_DEBUG("自由链表freeList[%d]取节点\n", index); return r; } //进行头删,将头指针指向下一个节点 *myFreeList = ret->freeListLink; return ret; } static void Deallocate(void* p, size_t n) { __TRACE_DEBUG("释放内存(p:%p, n: %u)\n", p, n); obj** myFreeList; obj* q = (obj*)p; if (n > __MAX_BYTES) { __MallocAllocTemplate<0>::Deallocate(p/*, n*/); return ; } myFreeList = freeList + FREELIST_INDEX(n); q->freeListLink = *myFreeList; *myFreeList = q; } static void* Reallocate(void* p, size_t oldSize,size_t newSize); }; //进行静态成员的初始化 template <bool threads, int inst> typename __DefaultAllocTemplate<threads, inst>::obj*\ __DefaultAllocTemplate<threads, inst>::freeList[__DefaultAllocTemplate<threads, inst>::__NFREELISTS] = {0}; template <bool threads, int inst> char* __DefaultAllocTemplate<threads, inst>::startFree = NULL; template <bool threads, int inst> char* __DefaultAllocTemplate<threads, inst>::endFree = NULL; template <bool threads, int inst> size_t __DefaultAllocTemplate<threads, inst>::heapSize = 0; template<bool threads,int inst> char* __DefaultAllocTemplate<threads, inst>::chunkAlloc(size_t size,int& nobjs) { char* ret; size_t TotalBytes = size* nobjs; size_t BytesLeft = (size_t)(endFree-startFree);//剩余的内存 if (BytesLeft >= TotalBytes) { //可以全部申请 __TRACE_DEBUG("内存池有足够的空间分配%d个对象\n",nobjs); ret = startFree; startFree += TotalBytes; return ret; } else if (BytesLeft >= size) { __TRACE_DEBUG("内存池只可以分配%d个对象\n", nobjs); //申请不到20块,但可以申请1块以上 nobjs = BytesLeft / size;//求出可以申请的内存块个数 TotalBytes = nobjs* size; ret = startFree; startFree += TotalBytes; return ret; } else { __TRACE_DEBUG("内存池分配不了1个对象\n", nobjs); //连1块的内存都木有了,向系统进行申请 size_t BytesToGet = 2 * TotalBytes + ROUND_UP(heapSize >> 4); //如果内存池中还有剩余,则将剩余的内存放入自由链表的对应节点中 if (BytesLeft > 0) { size_t index = FREELIST_INDEX(BytesLeft); obj** myFreeList = freeList + index; ((obj*)startFree)->freeListLink = *myFreeList; *myFreeList = (obj*)startFree; __TRACE_DEBUG("内存池将剩余的内存放入freeList[%d]中\n", index); } startFree = (char*)malloc(BytesToGet); __TRACE_DEBUG("从系统中获取%d内存\n", BytesToGet); if (startFree == NULL) { //从系统中没有申请到内存 //从更大的自由链表中去寻找 int i; obj** myFreeList; obj** p; for (i = size; i < __MAX_BYTES; i += __ALIGN) { myFreeList = freeList + FREELIST_INDEX(i); p = myFreeList; if (p != NULL) { *myFreeList = (*p)->freeListLink; startFree = (char*)p; endFree = startFree + i; return chunkAlloc(size, nobjs); } } //防止下一句的Malloc抛出异常 __TRACE_DEBUG("最后的办法,去一级空间配置器中进行查找\n"); endFree = 0; __MallocAllocTemplate<0>::Allocate(BytesToGet); } heapSize += BytesToGet; endFree = startFree + BytesToGet; return chunkAlloc(size,nobjs); } } //调用ChunckAlloc申请内存,并添加到自由链表中 template<bool threads,int inst> void* __DefaultAllocTemplate<threads, inst>::Refill(size_t size) { int nobjs = 20; obj ** myFreeList; void* ret; char* chunck = chunkAlloc(size, nobjs); if (nobjs == 1) return chunck; size_t index = FREELIST_INDEX(size); myFreeList = freeList + index; //将申请的内存加入到自由链表中 ret = chunck; obj* cur = (obj*)(chunck + size); freeList[index] = cur; for (int i = 2; i<nobjs; ++i) { cur->freeListLink = (obj*)(chunck + size*i); cur = cur->freeListLink; } cur->freeListLink = NULL; return ret; } template<bool threads,int inst> void* __DefaultAllocTemplate<threads, inst>::Reallocate(void*p, size_t oldSize,size_t newSize) { void* ret; size_t cpSize; if (oldSize > __MAX_BYTES && newSize < __MAX_BYTES) return Reallocate(p,newSize); if (ROUND_UP(oldSize) == ROUND_UP(newSize)) return p; ret = Allocate(new_sz); copySize = newSize > oldSize ? oldSize : newSize; memcpy(result, p, copySize); Deallocate(p, oldSize); return(ret); }
二级空间配置器是一串自由链表,通过内存池和自由链表共同管理内存
封装成为SimpleAlloc
typedef __DefaultAllocTemplate<0, 0> _alloc; template<class T, class Alloc = _alloc> class SimpleAlloc { public: static T* Allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::Allocate(n * sizeof (T)); } static T* Allocate(void) { return (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, sizeof (T)); } };
定义Trace,方便调试
#define __DEBUG__ #include<iostream> using namespace std; #include<string> #include<stdarg.h> static string GetFileName(const string& path) { char ch = '/'; #ifdef _WIN32 ch = '\\'; #endif size_t pos = path.rfind(ch); if (pos == string::npos) return path; else return path.substr(pos + 1); } // 用于调试追溯的trace log inline static void __trace_debug(const char* function, const char* filename, int line, char* format, ...) { #ifdef __DEBUG__ //输出调用函数的信息 fprintf(stdout, "【%s:%d】%s", GetFileName(filename).c_str(), line, function); //输出用户打的trace信息 va_list args; va_start(args, format); vfprintf(stdout, format, args); va_end(args); #endif } #define __TRACE_DEBUG(...) \ __trace_debug(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__);
测试函数
#include<vector> void TestAlloc() { vector<int*> v; SimpleAlloc<int> sa; for (size_t i = 0; i < 20; ++i) { __TRACE_DEBUG("申请内存第%d个\n",i+1); v.push_back((int*)sa.Allocate()); } int* p = (int*)sa.Allocate(); int i = 1; while (!v.empty()) { __TRACE_DEBUG("释放内存第%d个\n", i++); int* ptm = v.front(); v.pop_back(); sa.Deallocate(ptm); } sa.Deallocate(p); }
空间配置器的优缺点
空间配置器的优点
1、解决了外碎片的问题2、提高了用户使用内存的效率
空间适配器的缺点
1、虽然解决了外碎片,但是在链表的节点中,多多少少 还会存在内碎片的情况也就是说,空间适配器虽然解决了外碎片,但是引入了内碎片
2、空间配置器没有释放内存的机制,当用户频繁使用内存,后来使用完了归还时,仅仅只是归还给了空间配置器
而空间配置器并没有归还给操作系统,直到程序结束的时候才会归还
这就一定程度上导致了内存不用却一直占着的问题
相关文章推荐
- STL源代码分析--第二级空间配置器
- STL之空间配置器
- SGI_STL_空间配置器
- STL之空间配置器
- STL空间配置器源码及其分析
- [stl] SGI STL的空间配置器
- STL之空间配置器分析
- STL的二级空间配置器和malloc
- STL源码学习——空间配置器
- STL 二级空间配置器
- SGI STL 第二级空间配置器 内存池
- 【STL深入学习】SGI STL空间配置器详解(一)-第一级空间配置器
- STL中空间配置器的策略
- STL学习笔记——空间配置器
- STL 源码剖析读书笔记一:空间配置器
- 一步一步写STL:多级空间配置器(上)
- C++STL学习(13)STL深入(2) SGI STL空间配置器
- STL源码笔记之空间配置器
- SGI STL学习笔记(1):空间配置器(allocator)
- STL之空间配置器