STL-空间配置器

  • 在学习C++的过程中STL扮演了一个非常重要角色,STL中六大组件,容器,仿函数,迭代器,适配器,算法,空间配置器。我们最容易忽略的就是空间配置器
  • STL中空间配置器是为了各个容器高效的管理空间,在使用容器中感觉不到他的存在,但是学习他的原理是非常有必要的

为什么需要空间配置器?

在C/C++中程序员要申请空间通过malloc从堆上申请空间,这样频繁操作会有一些大的问题产生

  • 空间的申请与释放都需要程序员自己管理,很容易造成内存泄露
  • 频繁向系统申请空间,容易造成内存碎片
  • 频繁向系统申请小块内存,影响程序效率
  • 使用malloc申请空间,申请的空间有额外的浪费
  • 申请空间失败怎么处理?
    基于上面的一些问题,STL就需要一种高效的内存管理机制来管理整个空间,这就是STL空间配置器。

内存碎片

  • 内部碎片
    • 所有被分配的内存必须起始于可被4,8或者16(根据处理器决定)整除的的地址处,决定内存分配算法仅仅能把预定大小的内存块分配给用户,比如某一个用户请求大小为43字节的内存块时,因为没有正好合适的内存块,所以它可能会获取到44字节、48字节等稍微大一点的内存空间,这种因为四舍五入而产生的多余空间就是内部碎片
  • 外部碎片
    • 频繁的分配与回收物理页面会导致大量的,连续且小的页面夹杂在已经分配的物理页面之中,就会产生外部碎片
    • 比如内存中有连续大小为40字节的空间,编号0~39,如果第一次申请10个字节编号0-9,再申请5个字节编号10-14,如果这时将第一块内存释放,再次申请大于10个字节的空间,比如20个字节,因为刚被释放的空间不能满足条件,所以智能从15开始分配出20个字节的内存,如果程序以后都申请的是大于10个字节的空间,那么前10个字节的空间就永远得不到使用,造成内存外部碎片。
      在这里插入图片描述

空间配置器的实现

  • 这里简述SGI-STL的空间配置器实现。
  • 上述问题的关键之处在于频繁向系统申请小块内存,那怎么才算是小块内存?
  • SGI-STL以128作为小块内存与大块内存的分界线,将空间配置器分为两级结构,一级空间配置器处理大块内存,二级空间配置器处理小块内存
    一级空间配置器
    • 一级空间配置器原理非常简单,直接对malloc和free进行了简单封装
    • 特别注意的是,如果申请空间不成功,交给oom_malloc处理,检测是否有空间不足应对措施,如果没有就抛异常
    二级空间配置器
    • 二级空间配置器主要负责处理小于128字节的内存块,要避免上面的那些问题就需要一种可靠的机制来实现,首先我们可以想到事先申请好一块内存,如果用户需要就从内存池中存取,不需要直接去向系统申请。
    • 内存池:先申请一块比较大的内存做备用,如果需要内存就去内存池中取,当内存池中的空间不够时,再向内存去取,用户归还时直接还给内存池中即可,避免了向系统频繁申请小空间的效率低,内存碎片以及额外的空间浪费。
      在这里插入图片描述
    • 我们使用begin和finish来标记内存池的起始与终止位置,如果用户需要空间就将begin向后移动,那用户归还的空间放在哪里?肯定不可以将归还空间直接简单拼接在内存池中,那如何管理归还内存?
    • 我们想到用链表将没有使用的空间连接起来,如果使用链表进行管理,用户申请空间在查找合适的大小时时间复杂度为O(N),如果使用哈希桶来实现,查找时间复杂度可以达到O(1)。这样存取的时间复杂度也是极优的
    • SGI-STL使用内存池提高申请空间的速度以及减少额外的空间浪费,使用哈希桶提高用户获取空间的速度与高效管理
      在这里插入图片描述
    • 如图,用户只需要将归还的空间直接插入到哈希桶中即可,如果下次还需要申请这么大的空间,直接将哈希桶中的空间返回即可。
    • 如果用户申请的空间空间不是8的整数倍,就向上对其到8的整数倍处,这里主要是考虑64位机
    enum {__ALIGN = 8} // 用户所需内存不是8的整数倍,就向上对其到8的整数倍
    enum {__MAX_BYTES = 128} // 大小块内存的分界
    enum {__NFREELISTS = __MAX_BYTES/_ALIGN} // 使用哈希桶保存时哈希桶的个数
    
    static size_t ROUND_UP(size_t bytes) { // 向上对其到8的倍数
    	return (((bytes) + __ALIGN-1) & ~(__ALIGN-1));
    }
    	
    union obj {
    	union obj*  free_list_link;
    	char client_data[1];
    }; // 使用联合体维护链表结构
    // 当一个内存块空闲时,就再一个内存块中使用四个字节强制以这个obj连接到下一个空闲块,
    //当内存块交付给用户时,就直接存储用户数据
    
    // 根据用户提供字节数找到对应桶号
    static size_t FREELIST_INDEX(size_t bytes) {
    	return (((bytes) + __ALIGN-1)/__ALIGN-1);
    }
    
    //内存池中大内存的起始终止位置
    static char* start_free;
    static char* end_free;
    
    //记录空间配置器向系统中索要了多少的内存
    static size_t heap_size;
    
    • 接下来就是考虑如何管理空间,如果用户申请的空间大于128字节,就交给一级空间配置器来处理,二级空间配置器根据用户输入空间向上取整计算出hash桶号,如果这个哈希桶下面挂有空间,就直接将这块空间返回给用户如果没有挂空间,就向内存池中拿出空间向哈希桶中填充,并将第一块空间返回给用户
      在这里插入图片描述
    • 在向内存池索要空间时,系统会默认有一个参数nobjs(20),向内存池中申请这么多块,如果内存池中的空间够20块,就将这么多空间返回,如果不够20块但是满足至少可以提供一块,就修改nobjs的值,将这么多先进行返回,如果只能提供一块,就直接将这块返回给用户
      在这里插入图片描述
    • 如果内存池中剩余的空间不够返回一块,就需要重新给内存池中补充空间,先将内存中中剩余的空间挂在哈希桶中,然后使用系统堆向内存池中补充,如果向系统堆申请失败,就从哈希桶中找更大的内存补充,如果还是找不到,就交给一级空间配置器来补充
      在这里插入图片描述
    二级空间配置器的回收
    • 二级空间配置器的回收先判断空间大小,如果大于128字节就交给一级空间配置器来挥手,否则就找到对应的哈希桶,将内存直接挂在在哈希桶中
    static void deallocate(void* p, size_t n) {
    	obj* q = (obj*)p;
    	obj** my_free_list;
    
    	if(n > (size_t) __MAX_BYTES ) { // 交给一级空间配置器回收
    		malloc_alloc::deallocate(p, n);
    		return;
    	}
    
    	// 找到对应哈希桶, 直接挂在哈希桶中
    	my_free_list = free_list + FREEELIST_INDEX(n);
    	q->free_list_link = *my_free_list;
    	*my_free_list = q;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值