【C++】空间配置器

news2025/7/17 23:07:07

空间配置器,听起来高大上,那它到底是什么东西呢?

1.什么是空间配置器?

空间配置器是STL源码中实现的一个小灶,用来应对STL容器频繁申请小块内存空间的问题。他算是一个小型的内存池,以提升STL容器在空间申请方面的效率

image-20221018151529901

2.了解空间配置器

STL以128个字节为分界线,将空间配置器分为了一级和二级

2.1 一级

一级空间配置器中,allocate/deallocate函数实际上就是对malloc/free做了一个简单的封装

static void * allocate(size_t n)
{
    void *result = malloc(n);
    if (0 == result) result = oom_malloc(n);
    return result;
}

static void deallocate(void *p, size_t /* n */)
{
    free(p);
}

//这个函数是malloc失败的时候才会调用的
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
    void (* my_malloc_handler)();
    void *result;

    for (;;) {
        my_malloc_handler = __malloc_alloc_oom_handler;
        if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }//抛异常
        (*my_malloc_handler)();
        result = malloc(n);
        if (result) return(result);
    }
}

当malloc失败的时候,一级空间配置器会抛异常

2.2 二级

在二级空间配置器中,会先判断带申请空间大小是否大于128个字节,如果超过了,则会去调用一级空间配置器

# ifndef __SUNPRO_CC
enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
# endif  
/* n must be > 0      */
static void * allocate(size_t n)
{
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;

    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }
    my_free_list = free_list + FREELIST_INDEX(n);
    // Acquire the lock here with a constructor call.
    // This ensures that it is released in exit or during stack
    // unwinding.
    #       ifndef _NOTHREADS
    /*REFERENCED*/
    lock lock_instance;
    #       endif
    result = *my_free_list;
    if (result == 0) {
        void *r = refill(ROUND_UP(n));
        return r;
    }
    *my_free_list = result -> free_list_link;
    return (result);
};

/* p may not be 0 */
static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;

    if (n > (size_t) __MAX_BYTES) {
        malloc_alloc::deallocate(p, n);
        return;
    }
    my_free_list = free_list + FREELIST_INDEX(n);
    // acquire lock
    #       ifndef _NOTHREADS
    /*REFERENCED*/
    lock lock_instance;
    #       endif /* _NOTHREADS */
    q -> free_list_link = *my_free_list;
    *my_free_list = q;
    // lock is released here
}

二级空间配置器中,主要的几个成员变量如下

static obj * __VOLATILE free_list[__NFREELISTS]; //哈希桶
// Chunk allocation state.
static char *start_free;
static char *end_free;
static size_t heap_size;

这里是一个内存池+ 一个哈希桶,内存池的长度为heap_size

image-20221018152617677

哈希桶以8字节对齐,分为了16个桶。最开始的时候,free_list哈希桶是空的

image-20221018155632729

当我们需要小于128个字节空间的时候(以16字节为例),二级空间配置器会直接去上面的内存池当中申请20个16字节的空间,链接到16字节对应的哈希桶的位置(1号桶)

在下面的refill函数中可以看到这一点

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;//一次性申请20个
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    if (1 == nobjs) return(chunk);
    my_free_list = free_list + FREELIST_INDEX(n);

    /* Build free list in chunk */
      result = (obj *)chunk;
      *my_free_list = next_obj = (obj *)(chunk + n);
      for (i = 1; ; i++) {
        current_obj = next_obj;
        next_obj = (obj *)((char *)next_obj + n);
        if (nobjs - 1 == i) {
            current_obj -> free_list_link = 0;
            break;
        } else {
            current_obj -> free_list_link = next_obj;
        }
      }
    return(result);
}

8字节对齐的妙处

假设我们的list单个节点需要12个字节,set单个字节需要16个字节

当list需要空间的时候,二级空间配置器也会去申请16个字节的空间,而不会去申请12个字节。这时候,当list将用完的空间还回来之后,就能拿过去给set用了!

关于哈希桶的特殊处理

当我们用完了申请的空间,准备“释放”的时候,二级空间配置器会将释放回来的空间插入进哈希桶(头插)

这里有一个特殊处理,当用完的空间,或者说是刚申请的空间插入进哈希桶的时候,二及空间配置器会将其强转为一个obj类型,这个类型的前4/8个字节会用来存放一个指针,指向下一个空间,以此构成链表

union obj {
    union obj * free_list_link;
    char client_data[1];    /* The client sees this.        */
};

image-20221018160755549

当我们再次需要这部分大小的空间的时候,只需要将哈希桶头指针的指向直接修改,就能链接上下一个空间,并将之前头指针指向的空间拿去用

用的时候也不用管之前在此处存放的指针,毕竟下一次放回来的时候,二级空间配置器会来处理它的


内存池空间不够用了咋办?

之前提到了,二级空间配置器里面有一个小内存池。要是这个内存池用完了,要咋整呢?

二级空间配置器想出来了一种很骚的做法:

  • 假设当前我们需要24字节的空间
  • 对应的桶下面没有空间给我们用
  • 内存池也用光了
  • 但是48字节的桶下面还有空间可以用
  • 直接把这个空间拿过来,拆成两个24链接到24的桶下面!

这样便有效提高了空间利用率,也避免了realloc造成的时间消耗。

当然,要是桶里面也没有多余空间了,那就只能老老实实去扩容了

只能大的拆小的,小的空间不连续无法组成大的

//二级空间配置器里面的reallocate封装
template <bool threads, int inst>
void*
__default_alloc_template<threads, inst>::reallocate(void *p,
                                                    size_t old_sz,
                                                    size_t new_sz)
{
    void * result;
    size_t copy_sz;

    if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {
        return(realloc(p, new_sz));
    }
    if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);
    result = allocate(new_sz);
    copy_sz = new_sz > old_sz? old_sz : new_sz;
    memcpy(result, p, copy_sz);
    deallocate(p, old_sz);
    return(result);
}

2.3 关于单例的说明

在同一个进程里面,空间配置器只会有一个

但STL库中的空间配置器并没有把自己设计成单例类,而是用了一个间接的做法

static obj * __VOLATILE free_list[__NFREELISTS]; //哈希桶
// Chunk allocation state.
static char *start_free;
static char *end_free;
static size_t heap_size;

那就是所有成员变量都是static静态的

我们知道,在类和对象中,静态成员变量是属于整个类的,所以它也是一种单例模式!


2.4 空间配置器的优点

  • 空间配置器的时间复杂度是O(1),申请空间的效率很高
  • 空间配置器能解决频繁申请空间导致的内存碎片问题

当我们使用STL容器频繁申请小块空间(比如list)就容易导致堆区中有非常多的小块空间被使用,而无法申请一个大的空间

空间配置器提前申请了一个大块空间再私下管理,可以有效解决这个问题

内存外碎片/内碎片

  • 外碎片:多次申请小块空间造成。有足够内存,但是不连续,无法申请大块内存
  • 内碎片:list只需要12个字节,而空间配置器因为内存对齐而给了它16个字节,浪费了4个字节造成的内存碎片

3.容器和空间配置器

之前学习stl容器的时候,就在定义中看到过这里的alloc默认模板参数

image-20221018164053947

这里默认传的便是stl库中的二级空间配置器

只要你自己写的空间配置器符合stl的接口需求,你便可以将自己的空间配置器传入此处让容器使用!

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
typedef __default_alloc_template<false, 0> single_client_alloc;

在list中,alloc又被封装了一层,用来处理节点申请的问题

template <class T, class Alloc = alloc>
class list {
protected:
  typedef void* void_pointer;
  typedef __list_node<T> list_node;
  typedef simple_alloc<list_node, Alloc> list_node_allocator;
template<class T, class Alloc>
class simple_alloc {

public:
    static T *allocate(size_t n)
                { return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
    static T *allocate(void)
                { return (T*) Alloc::allocate(sizeof (T)); }
    static void deallocate(T *p, size_t n)
                { if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
    static void deallocate(T *p)
                { Alloc::deallocate(p, sizeof (T)); }
};

随后,当创建节点、销毁节点的时候,list就会调用封装好的simple_alloc来处理空间

link_type get_node() { return list_node_allocator::allocate(); }
void put_node(link_type p) { list_node_allocator::deallocate(p); } 
link_type create_node(const T& x) {
    link_type p = get_node();
    __STL_TRY {
      construct(&p->data, x);//调用节点的构造函数
    }
    __STL_UNWIND(put_node(p));
    return p;
  }
  void destroy_node(link_type p) {
    destroy(&p->data);//调用节点的析构函数
    put_node(p);
  }
  • construct通过定位new显式调用节点的构造函数
  • destroy通过指针来调用析构函数
template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);
}

stl_tree.h中可以看到库里面的红黑树也对空间配置器进行了类似的封装

template <class Key, class Value, class KeyOfValue, class Compare,
          class Alloc = alloc>
class rb_tree {
protected:
  typedef void* void_pointer;
  typedef __rb_tree_node_base* base_ptr;
  typedef __rb_tree_node<Value> rb_tree_node;
  typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;
  typedef __rb_tree_color_type color_type;

结语

关于空间配置器的内容了解这些就差不多啦!

其实就是通过库里面的这个设计来了解一个提高小空间内存申请效率的方法,感觉还是很666的

有啥问题可以在评论提出哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/361542.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI 生成二次元女孩,免费云端部署(仅需5分钟)

首先需要google的colab&#xff0c;免费版本GPU有额度。其次&#xff0c;打开github网站&#xff0c;选择一个进入colab,修改代码 !apt-get -y install -qq aria2 !pip install -q https://github.com/camenduru/stable-diffusion-webui-colab/releases/download/0.0.16/xforme…

webstom找不到vue全局组件

我真多服气&#xff0c;引入了自动组件注册 // 自动引入组建import { ElementPlusResolver } from unplugin-vue-components/resolversComponents({directoryAsNamespace: true,resolvers: [ElementPlusResolver()]}),生成了 components.d.ts 但是我在webstom中定义了标签 除非…

2023从0开始学性能(1) —— 性能测试基础【持续更新】

背景 不知道各位大佬有没遇到上面的情况&#xff0c;性能这个东西到底是什么&#xff0c;还是以前的358原则吗&#xff1f;明显并不是适用于现在了。多次想踏入性能测试门槛都以失败告终&#xff0c;这次就以系列的方式来督促自己真正踏进性能测试的门槛。 什么是性能测试 通…

九龙证券|机制改革激发转融券活力 全面注册制释放两融展业新空间

在全面注册制准则规矩正式发布的同时&#xff0c;修订后的转融通事务规矩也应约与商场碰头。2月17日&#xff0c;中证金融发布《中国证券金融公司转融通事务规矩&#xff08;试行&#xff09;&#xff08;2023年修订&#xff09;》等规矩&#xff08;简称“转融通新规”&#x…

操作系统(day12)-- 虚拟内存;页面分配策略

虚拟内存管理 虚拟内存的基本概念 传统存储管理方式的特征、缺点 一次性&#xff1a; 作业必须一次性全部装入内存后才能开始运行。驻留性&#xff1a;作业一旦被装入内存&#xff0c;就会一直驻留在内存中&#xff0c;直至作业运行结束。事实上&#xff0c;在一个时间段内&…

秒杀系统设计

1.秒杀系统的特点 瞬时高并发 2.预防措施 2.1.流量限制 对于一个相同的用户&#xff0c;限制请求的频次对于一个相同的IP&#xff0c;限制请求的频次验证码&#xff0c;减缓用户请求的次数活动开启之前&#xff0c;按钮先置灰&#xff0c;防止无效的请求流入系统&#xff0…

企业数智化转型在即,看看低代码软件公司如何做!

在信息爆炸的现代社会中&#xff0c;利用先进技术为企业提升办公协作效率&#xff0c;是一件事半功倍的事。当前&#xff0c;数字化转型升级已经是发展趋势&#xff0c;不少企业已经在朝着数智化转型方向迈进。作为一家低代码软件公司&#xff0c;流辰信息看到了市场发展前景&a…

想玩好ChatGPT?不妨看看这篇文章

相信点进来的铁汁,此时已经对 ChatGPT 有所了解,并想上手体验一番 首先大伙儿要注意,不要被骗了。 现在很多商家提供的 ChatGPT 服务,不仅价格奇高,而且据我所知,有些压根不是 ChatGPT 。 想玩最好去官网注册,具体方法大伙自个儿查一查嗷。 怎么用好 ChatGPT 虽然 …

vue uniapp 微信小程序 搜索下拉框 模糊搜索

vue uniapp 微信小程序 搜索下拉框 模糊搜索 话不多说 直接贴代码 template <template><view class"index"><view class"index_top"><view class"list_text"><view class"list_top_title"><text cl…

真我air笔记本电脑怎么重装Win10系统?

真我air笔记本电脑怎么重装Win10系统&#xff1f;最近真我air笔记本电脑挺多用户购买的&#xff0c;因为这款电脑性价比比较高&#xff0c;适合学生和一些办公人员来使用。但是系统预制了Win11系统&#xff0c;有用户想要将系统重装到Win10来使用。那么如何去进行系统的重装呢&…

【深度学习编译器系列】2. 深度学习编译器的通用设计架构

在【深度学习编译器系列】1. 为什么需要深度学习编译器&#xff1f;中我们了解到了为什么需要深度学习编译器&#xff0c;和什么是深度学习编译器&#xff0c;接下来我们把深度学习编译器这个小黑盒打开&#xff0c;看看里面有什么东西。 1. 深度学习编译器的通用设计架构 与…

易语言中控开发

效果展示 demo下载 点击下载 需要实现功能 服务端和客户端的连接客户端向服务端发送数据服务端向客户端发送数据中控一对多控件设置 1.服务端和客户端的连接 1.新建服务端.e 2.新建客户端.e 3.服务端启动窗口增加组件:服务器 4.客户端启动窗口增加组件:客户端 5.设置服务器…

python 绘图 —— 绘制从顶部向底部显示的柱形图[ax.bar()]

python 绘图 —— 绘制从顶部向底部显示的柱形图[ax.bar()] 效果图如下所示&#xff1a; 就是这个样子&#xff0c;一般比较少见将柱形图从上往下绘制的。可能是会为了更好的展示数据对比结果吧。这里绘图的主要思路如下&#xff1a; 利用ax.twinx()这个函数生成一个新的x轴…

横板格斗类游戏实战:游戏数值策划表

游戏数值表在游戏设计中非常的关键&#xff0c;策划可以通过表格工具与表格公式来做好游戏的数值&#xff0c;程序当表格是一个配置文件&#xff0c;直接读入数据即可。游戏数值策划表是数值策划与程序沟通对接的主要的方式, 所以对项目开发来说非常重要。 对啦&#xff01;这…

工业树莓派和PLC怎么选?

一、 什么是虹科工业树莓派 1、树莓派 在了解虹科工业树莓派之前&#xff0c;首先要了解一下什么是树莓派。树莓派是一款基于ARM的小型电脑&#xff0c;在树莓派上提供丰富的接口&#xff0c;能够实现较多功能。它同样是开发人员的最爱&#xff0c;其搭载Linux系统&#xff0…

2023年,智能家居实体门店如何选品?

作者 | 启明 编辑 | 小沐 出品 | 智哪儿 zhinaer.cn2023年&#xff0c;是智能家居实体门店的机会与破局之年&#xff0c;作为智能家居实体门店老板&#xff0c;我们应该具备什么样的增长思维呢&#xff1f;上篇文章智哪儿谈了智能家居增长思维之流量思维 &#xff0c;这篇文章我…

实时渲染新技术能给业主带来哪些价值?点量云

目前在数字孪生三维可视化项目领域&#xff0c;本地部署和Webgl方案是使用比较多的。本地部署方案&#xff0c;根据项目的需要配备几台高性能电脑&#xff0c;在电脑上安装相应的三维可视化模型即可&#xff0c;通常使用的频率不是很高。而Webgl方案&#xff0c;相比本地部署&a…

数字经济讨论题

自2001年以来&#xff0c;Alphabet&#xff08;Google&#xff09;已进行了200多次并购。下面列出了并购年份。选择Alphabet进行的三笔并购讨论这些并购是如何使Alphabet拥有新的或增强的现有业务领域重要的是考虑何时进行所选择的收购。谷歌已经从一家提供互联网搜索引擎的公司…

制造企业为何要上数字化工厂系统?

以目前形势来看&#xff0c;数字化转型是制造企业生存的关键&#xff0c;而数字化工厂管理系统是一个综合性、系统性的工程&#xff0c;波及整个企业及其供应链生态系统。数字化工厂系统所要实现的互联互通系统集成、数据信息融合和产品全生命周期集成&#xff0c;将方方面面的…

火热报名 | DockQuery 1.2 beta版本体验官开启招募!

DockQuery是什么&#xff1f; DockQuery 代号「天狼」&#xff0c;是图尔兹全新自研的一款专业新型数据库桌面客户端&#xff0c;专为信创背景下国内外数据库开发/管理而设计&#xff0c;全面覆盖信创数据库目录、支持国内外操作系统。 目前&#xff0c;DockQuery 仅以社区版…