C++ STL之allocator

1. allocator 分配器

1.1 分配器allocator

  1. VC,BC,GCC中,allocator调用::operator new来分配内存,而::operator new是调用malloc来分配内存,所以allocator最终还是调用malloc。deallocators里调用::operator new来释放内存,而delete调用的是free,所以deallocator最终调用free
    在这里插入图片描述
    在这里插入图片描述

  2. malloc会有一些额外开销。如下图所示,size是我们申请的大小,malloc会包上一些overhead,malloc给的内存大小比我们申请的大小要多了一些(红色部分是cookie)。
    在这里插入图片描述

  3. GCC2.9中中虽然提供了allocators,但是其容器并没有采用allocator,而是采用了alloc,采用链表优化了malloc,减少cookie的存储。malloc里面的cookie用于记录分配内存的大小,是那个上面和下面的那个0000041两行,delete读到这个也会知道object的大小。但是对于容器来说,每个元素的大小是一样的,内存分配的原理可以简化。没有必要为每个元素都记录大小,alloc设计了16条链表,每个链表负责特定大小的区块,并串起来。#0号链表负责8个字节大小的,#7号链表56个字节大小的,依次类推。#15号负责16*8=128个字节大小的。容器调用malloc时,容器的元素大小会被调整到8的倍数,比如50调整到56。这样每块元素就不用都带有cookie,操作系统malloc,申请一大块内存,然后切出一个没有cookie的部分。这里终究会使用malloc,每次拿malloc都会带有一个cookie。但是通过减少系统调用malloc,提高效率。
    在这里插入图片描述

  4. 但是GCC4.9之后,又改回了以前的设计,alloc还在,即__pool_alloc。采用以下代码可以使用__pool_alloc

    #include "iostream"
    #include "ext/pool_allocator.h"
    #include "algorithm"
    #include "vector"
    using namespace std;
    int main() {
        vector<string, __gnu_cxx::__pool_alloc<string>> v;
    }
    

1.2 SGI特殊的空间配置器,std::alloc参考链接

  1. 一般来说,c++内存配置和释放操作如下:

    class Foo {...};
    Foo* pf = new Foo;	//配置内存,然后构造对象
    delete pf;			//将对象析构,然后释放内存
    

    这其中的new内含两个阶段操作:1、调用::operator new 配置内存。2、调用构造函数,构造对象内容delete也内含两个阶段操作:1、调用析构函数。2、调用::operator delete 释放内存。
    为了精密分工,STL 将这两个阶段操作区分开来。内存配置操作由 成员函数 allocate() 负责,内存释放由deallocate() 负责;对象构造由 construct() 负责,对象析构则由 destroy() 负责。

  2. 在内存分配的过程中,会有几个问题需要考虑。

    • 小块内存带来的内存碎片问题。
    • 小块内存频繁申请释放带来的性能问题。

    基于上述问题,SGI设计了双层级配置器,第一级配置器直接使用malloc()free(),第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,视为足够大,便调用第一级配置器;当配置区块小于128bytes视为过小,为了降低overhead(1.1中提到过),采用复杂的内存池memory pool整理方式(链表形式)。

1.2.1 第一级配置器
  1. 第一级配置器的流程如下:
    在这里插入图片描述
  2. SGI的第一级配置器以 malloc(), free(), realloc() 等C函数执行实际的内存配置、释放、重配置操作。当 malloc 或者 realloc 调用不成功后,改调用 oom_malloc() 和 oom_realloc() 。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存。但如果“内存不足处理例程”未被客户端设定,则直接抛出 bad_alloc 异常,或者终止程序。
1.2.2 第二级配置器
  1. 二级配置器使用内存池+自由链表的形式避免了小块内存带来的碎片化,提高了分配的效率,提高了利用率。它是用一个16个元素的自由链表(free_list)来管理的,每个位置的内存大小都是8的倍数,分别为:8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128。

    free_list的节点结构如下:

    union obj
    {
    	union obj* free_list_link;
    	char client_data[1];
    }
  2. 二级配置器内存分配:

    主要分为四种情况:

    1. free_list列表中有空余内存。如果申请3个字节的内存,则所需空间大小提升为8的倍数,然后去 free_list 中查找相应的链表,如果 free_list[i] 不为空,则返回第一个元素,然后把头指针往后移。

    2. free_list 列表中没有空余,但内存池不为空。首先检验内存池中的大小是不是比申请的内存大(缺省值申请20个),比如申请20*8的内存,如果足够,则分配相应内存,将其中一个分配给用户使用,其它的挂在相应的 free_list 中。如果内存池不够大,只够几个内存分配,则就分配这几个,把相应的数据返回。如果连一个都不够则执行第三中情况。

    3. free_list列表中没有空余,内存池也不够。调用malloc重新分配内存,分配时会多分配一倍的内存,把相应的内存挂到free_list下,剩余的放到内存池中。

    4. free_list列表中没有空余,内存池也不够,malloc也失败。则调用一级空间配置器,里面会有循环处理,或者抛出异常。

  3. 二级配置器内存回收

    当用户从二级空间配置器中申请的内存被释放时,二级空间配置器将回收的内存插入到对应的 free_list 中。其流程如下:
    在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值