文章目录
- 1. 什么是空间配置器
- 2. 为什么需要空间配置器
- 3. SGI-STL空间配置器实现原理
- 3.1 一级空间配置器
- 3.2 二级空间配置器
- 3.2.1 内存池
- 3.3 空间配置器的再次封装
- 3.4 对象的构造与释放
- 4. 与容器结合
1. 什么是空间配置器
空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的。
2. 为什么需要空间配置器
前面在模拟实现vector、list、map、unordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处:
1.空间申请与释放需要用户自己管理,容易造成内存泄漏。
2.频繁向系统申请小块内存,容易造成内存碎片和影响程序运行效率。
3.直接使用malloc与new进行申请,每块空间前有额外空间浪费。
4.申请空间失败怎么应对。
5.未考虑线程安全问题。
因此需要设计一块高效的内存管理机制。
那么空间配置器,malloc,堆的关系如下:
用空间配置器效率会更高。
3. SGI-STL空间配置器实现原理
以上提到的几点不足之处,最主要还是:频繁向系统申请小块内存造成的。
那什么才算是小块内存?
SGI-STL以128作为小块内存与大块内存的分界线,将空间配置器其分为两级结构,一级空间配置器处理大块内存,二级空间配置器处理小块内存。
3.1 一级空间配置器
一级空间配置器原理非常简单,直接对malloc与free进行了封装,并增加了C++中set_new_handle思想。
一级空间配置器的名字是:__malloc_alloc_template
里面有对malloc和free的封装,那么这个oom_malloc是什么呢?
3.2 二级空间配置器
二级空间配置器专门负责处理小于128字节的小块内存。
如何才能提升小块内存的申请与释放的方式呢?
SGI-STL采用了内存池的技术来提高申请空间的速度以及减少额外空间的浪费,采用哈希桶的方式来提高用户获取空间的速度与高效管理。
二级空间配置器的名字是:__default_alloc_template
3.2.1 内存池
内存池就是:先申请一块比较大的内存块已做备用,当需要内存时,直接到内存池中去取,当池中空间不够时,再向内存中去取,当用户不用时,直接还回内存池即可。
避免了频繁向系统申请小块内存所造成的效率低、内存碎片以及额外浪费的问题。
这里是用两个指针来管理这个内存池,当我们需要一块小内存时,start就会往后移动,到我们需要的大小时就给我们使用了。
如果我们不使用这些小内存块时,我们怎么还回去呢?
这里是使用哈希桶给挂起来,因为查找合适的小块内存时效率比较低。
那是否需要128桶个空间来管理用户已经归还的内存块呢?
答案是不需要,因为用户申请的空间基本都是4的整数倍,其它大小的空间几乎很少用到。因此:SGI-STL将用户申请的内存块向上对齐到了8的整数倍。
二级空间配置器的整体申请内存情况如下:
一开始内存池和桶里面都是空的,此时我们要申请10字节的空间。那么我们就开始计算在那个桶,在1号桶里面没有空间,就会去内存池申请,内存池也没有,就去malloc了。
malloc会一次申请很大一块空间。然后我们需要10字节,它会一次给我们很多个10字节,把头结点的空间给我们,剩下的挂在桶中。
因为这样下次我们再申请10字节的空间,就会去桶里面直接找到,然后头删给我们,这样时间复杂度就在O(1)。当我们这段空间不需要在使用了,就头插到桶中,复杂度也是O(1)。
那么这里的内存空间是如何连起来的?
源码里是用了一个联合体,原因是:在每段内存空间中会存下一个内存空间的地址,在32位下就是4字节,在64位下就是8字节。当我们把一段空间给用户时,地址就会消失,当不用时,重新头插,地址又有了。这就是为什么以8字节对齐,而不是按照4字节对齐。
流程图如下:
3.3 空间配置器的再次封装
在C++中,用户所需空间可能是任意类型的,有单个对象空间,有连续空间,每次让用户自己计算所需空间总大小不是很友好,因此SGI-STL将空间配置器重新再封装了一层:
3.4 对象的构造与释放
一切为了效率考虑,SGI-STL决定将空间申请释放和对象的构造析构两个过程分离开,因为有些对象的构造不需要调用析构函数,销毁时不需要调用析构函数,将该过程分离开可以提高程序的性能:
4. 与容器结合
本例子给出list与空间配置器是如何结合的: