C++ STL的内存优化

1)二级配置器结构

STL内存管理使用二级内存配置器。

1、第一级配置器

第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重新配置等操作,并且能在内存需求不被满足的时候,调用一个指定的函数。

一级空间配置器分配的是大于128字节的空间

如果分配不成功,调用句柄释放一部分内存

如果还不能分配成功,抛出异常

2、第二级配置器

在STL的第二级配置器中多了一些机制,避免太多小区块造成的内存碎片,小额区块带来的不仅是内存碎片,配置时还有额外的负担。区块越小,额外负担所占比例就越大。

3、分配原则

如果要分配的区块大于128bytes,则移交给第一级配置器处理。

如果要分配的区块小于128bytes,则以内存池管理(memory pool),又称之次层配置(sub-allocation):每次配置一大块内存,并维护对应的16个空闲链表(free-list)。下次若有相同大小的内存需求,则直接从free-list中取。如果有小额区块被释放,则由配置器回收到free-list中。

当用户申请的空间小于128字节时,将字节数扩展到8的倍数,然后在自由链表中查找对应大小的子链表

如果在自由链表查找不到或者块数不够,则向内存池进行申请,一般一次申请20块

如果内存池空间足够,则取出内存

如果不够分配20块,则分配最多的块数给自由链表,并且更新每次申请的块数

如果一块都无法提供,则把剩余的内存挂到自由链表,然后向系统heap申请空间,如果申请失败,则看看自由链表还有没有可用的块,如果也没有,则最后调用一级空间配置器

2)二级内存池

二级内存池采用了16个空闲链表,这里的16个空闲链表分别管理大小为8、16、24......120、128的数据块。这里空闲链表节点的设计十分巧妙,这里用了一个联合体既可以表示下一个空闲数据块(存在于空闲链表中)的地址,也可以表示已经被用户使用的数据块(不存在空闲链表中)的地址。


1、空间配置函数allocate

首先先要检查申请空间的大小,如果大于128字节就调用第一级配置器,小于128字节就检查对应的空闲链表,如果该空闲链表中有可用数据块,则直接拿来用(拿取空闲链表中的第一个可用数据块,然后把该空闲链表的地址设置为该数据块指向的下一个地址),如果没有可用数据块,则调用refill重新填充空间。

2、空间释放函数deallocate

首先先要检查释放数据块的大小,如果大于128字节就调用第一级配置器,小于128字节则根据数据块的大小来判断回收后的空间会被插入到哪个空闲链表。

3、重新填充空闲链表refill

在用allocate配置空间时,如果空闲链表中没有可用数据块,就会调用refill来重新填充空间,新的空间取自内存池。缺省取20个数据块,如果内存池空间不足,那么能取多少个节点就取多少个。

从内存池取空间给空闲链表用是chunk_alloc的工作,首先根据end_free-start_free来判断内存池中的剩余空间是否足以调出nobjs个大小为size的数据块出去,如果内存连一个数据块的空间都无法供应,需要用malloc取堆中申请内存。

假如山穷水尽,整个系统的堆空间都不够用了,malloc失败,那么chunk_alloc会从空闲链表中找是否有大的数据块,然后将该数据块的空间分给内存池(这个数据块会从链表中去除)。

3、总结:

1. 使用allocate向内存池请求size大小的内存空间,如果需要请求的内存大小大于128bytes,直接使用malloc。

2. 如果需要的内存大小小于128bytes,allocate根据size找到最适合的自由链表。

a. 如果链表不为空,返回第一个node,链表头改为第二个node。

b. 如果链表为空,使用blockAlloc请求分配node。

x. 如果内存池中有大于一个node的空间,分配竟可能多的node(但是最多20个),将一个node返回,其他的node添加到链表中。

y. 如果内存池只有一个node的空间,直接返回给用户。

z. 若果如果连一个node都没有,再次向操作系统请求分配内存。

①分配成功,再次进行b过程。

②分配失败,循环各个自由链表,寻找空间。

I. 找到空间,再次进行过程b。

II. 找不到空间,抛出异常。

3. 用户调用deallocate释放内存空间,如果要求释放的内存空间大于128bytes,直接调用free。

4. 否则按照其大小找到合适的自由链表,并将其插入。

你可能感兴趣的:(C++ STL的内存优化)