引言
我们在使用STL的容器时,会发现容器中的数据在到达一定数量时,就会进行扩容操作,也就会申请空间。频繁的进行扩容时,就会频繁地向操作系统申请空间,这样极大的影响了操作系统地效率。为了解决这一问题,操作系统就说,我直接给你一块内存空间,你就从这块内存空间申请和释放内存,就不用直接过来找我了,里面的内存不够了再来找我。于是,就有了空间配置器这一概念。所以总的来说,空间配置器就是是 C++ 中用于管理内存分配和释放的机制,在STL库中,容器和其他一些组件都会使用空间配置器来分配和管理内存。
结构
空间配置器有两级结构,一级空间配置器是用来处理大块内存,而二级空间配置器则处理小块内存。SGI-STL规定以128字节作为小块内存和大块内存的分界线。
为什么这么分呢?
因为STL容器一般申请的都会是小块的内存,也就是小于128字节的内存,旨在解决频繁向系统申请小块内存时出现的问题;如果申请的空间大于128字节则调用一级空间配置器。
一级空间配置器
原理
底层对malloc与free进行封装,引入c++中的set_new_handle思想,该思想是提供一种机制来处理内存分配失败的情况,总的来说就是如果内存分配失败,它会调用一个处理函数来尝试解决问题,比如释放一些资源或采取其他适当的措施,以避免抛出异常。
二级空间配置器
前面我们说到二级空间配置器处理小块内存,即小于128字节的内存。而在SGI-STL中采用了内存池的技术来提高申请空间的速度。
内存池技术:
内存池技术是一种内存管理技术,主要用于优化程序中动态内存分配和释放的效率,减少内存碎片,提高程序运行速度。内存池通过预先分配一大块内存,留作备用,当申请内存时,从池中取出一块动态分配,当释放时,将释放的内存放回到池内,再次申请就可以从池里取出来使用。这种技术旨在提高资源的利用率,保证程序占有的资源数量,同时减少由于频繁申请和释放内存导致的内存碎片问题,从而提高程序和操作系统的性能。
与此同时也出现了一个问题,就是归还内存的时候,不知道该内存原来在内存池中的位置,也就无法归还,于是我们引入哈希桶这一个概念
哈希桶技术
哈希桶技术是哈希表实现中的一种数据结构,用于解决哈希冲突问题。哈希桶的工作原理基于链地址法,即当发生哈希冲突时,新的键-值对可以直接插入到相应的桶中,形成一个键-值对的链表。这种实现方式允许在一个桶中存储多个键-值对,从而提高了查找效率。
那我们需要用128个桶对这128字节的每个字节的空间进行管理吗?
事实上,在SGI-STL中只用到了16个哈希桶,即每个哈希桶管理128/16=8字节。事实上在大部分时候,用户申请的空间基本上都是4的整数倍,那为什么是8而不是4呢?因为前面我们提到哈希桶的工作原理基于链地址法,即通过链表将所有内存块连接,所以每个内存块需要保存下一个内存块的地址,而我们知道地址在64位系统下刚好为8字节。
内存碎片
分为外碎片和内碎片
外碎片
外部碎片则指的是还没有被分配出去的内存空闲区域,但由于这些空闲区域太小,无法分配内存空间。这些存储块位于已分配区域的外部,它们的总内存可能满足当前申请的长度要求,但由于地址不连续,系统无法满足当前申请。由于用户频繁地申请小块内存,使得整块内存中被申请的内存块不连续,导致某次申请内存时如果需要申请一大块内存,内存空间够,但是由于不连续,无法申请到。
内碎片
内部碎片指的是已经被分配出去却不能被利用的内存空间。这些存储块位于区域或页面的内部,即使进程占有了这些存储块,系统也无法利用它们,直到进程释放或结束时,系统才可能利用这些存储块。内存申请时按一定规则对齐,就会导致内存碎片给的内存数比实际要的内存数多,导致空间浪费。事实上SGI-STL将用户申请的内存块向上对齐到了8字节的整数倍,就造成了内碎片问题。
总结
当容器进行扩容时,如果申请的空间是大于128字节,直接向一级空间配置器申请。如果小于128字节,则先查找哈希桶对应大小位置是否为空,若不为空,则直接从该位置申请空间,如果该位置为空,向内存池申请。当用户向内存池申请一块大小为n的空间时,为了提高效率,内存池会切割若干块大小为n的空间,将其中一个分配给用户,其余的会连接到哈希桶对应下标处,避免下一次查找哈希桶对应大小位置为空。当内存池空间不够了则向操作系统申请空间;