STL——空间配置器

空间配置器是STL六大组件之一,它和其他五个组件相互配合,起着很关键的作用。

  • 容器:各种数据结构、如vector、list、stack、deque、queue、set、map、unordered_map等等
  • 算法:各种算法,如sort、serach、copy、erase 提供了对容器的增删查改等等
  • 迭代器:迭代器通常用于遍历容器元素的时候,扮演者容器和算法之间的胶合剂,它封装了底层实现的细节、给用户提供了统一的接口去调用,极大的降低了成本。
  • 适配器:stack/queue/priority_queue中就有涉及到适配器,它们的默认配接的就是deque,像map和list中的反向迭代器也是适配器
  • 仿函数:是实现了operator()的类,这个类对象可以像函数一样去使用
  • 空间配置器:顾名思义,空间配置器就是给各个容器高效的管理空间的。

其实看源码的时候,其中alloc就是空间配置器,有了它就可以解决一下几个问题

  • 频繁的像系统申请小块的内存,这样容易造成内存碎片
  • 如果直接使用malloc new,内存就可能有浪费
  • 申请空间失败
  • 在多线程中,线程不安全的问题...

SGI-STL空间配置器中,主要是解决了频繁的像系统申请小块内存

在SGI版本中,是以128为小块空间的边界,将大于128的称之为一级空间配置器,小于128的称为二级空间配置器

一级空间配置器(__malloc_alloc_template)

一级空间配置器其实就是对malloc和free的封装,并增加了C++中set_new_handle思想

如果申请成功了就直接返回,失败就交给oom_malloc处理,其中for里面就是检测用户是否设置了空间不足的应对措施,如果没有设置,抛异常模式为new方式

deallocate就是对free的封装

二级空间配置器(__default_alloc_template)

二级空间配置器专门负载处理小于128字节的小内存块,在SGI版本中采用了内存池的方式去申请空间和释放空间。

其中有以下四个成员函数,之所以定义为static,是因为所有容器都是向同一块内存池中申请和释放的,所以是共享的同一块空间。

可以发现SGI采用了哈希桶的方式提高用户获取空间的速度与高校管理。

内存池:会先申请一块大的内存作为备用,当需要的内存小于128的时候,就会直接向哈希桶里面申请内存和释放内存,这样就避免了频繁的向系统申请小内存所造成的内存效率低,内存碎片的问题

SGI版本中内存池技术其实并没有以链表的方式对用户已经归还的空间进行管理,因为用户申请内存的时候在查找方面其效率比较低,所以采用了哈希桶的方式。在查找方面平均就做到了O(1)。但其实SGI中并没有用到128个桶,它用到的是8 16 24...,8的倍数。

那么这时候就有一个疑问:

用户申请的空间通常是4的倍数,为什么要内存对齐到8呢?

我们设想这样一个场景,如果我们申请40字节的空间,但是二级空间配置器没有了,malloc也失败了,那么它就会像下一个有空间的内存,比如64字节来用,将64字节分为40和24,分别挂到哈希桶中。  这也是为什么要对齐到8的一个优势

template <bool threads, int inst>
class __default_alloc_template {

private:
    enum {__ALIGN = 8}; //如果用户需要的内存不是8,对齐到8
    enum {__MAX_BYTES = 128}; //内存块的边界
    enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; //采用哈希桶保存小块内存映射时桶的个数

   //如果用户所需要的每次块不是8的倍数,对齐到8的倍数
  static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
  }
__PRIVATE:
    //这里遵循了好的习惯,要知道如果用宏对调试等都不方便,而且联合体可以对空间利用到极致
  union obj {
        union obj * free_list_link;
        char client_data[1];    /* The client sees this.        */
  };
private:
# ifdef __SUNPRO_CC
    static obj * __VOLATILE free_list[]; 

    static obj * __VOLATILE free_list[__NFREELISTS]; 
    //哈希函数,根据用户提供的字节数找到对应的桶号
  static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
  }
    
  static void *refill(size_t n);
  static char *chunk_alloc(size_t size, int &nobjs);

 //start_free和end_free用来标记内存池中大块内存的起始和末尾
  static char *start_free;
  static char *end_free;
    //用来记录该空间配置器已经向系统索要了多少的内存
  static size_t heap_size;

二级空间配置器的逻辑如下

可以看到耳机空间配置器申请空间的时候,会根据空间是否大于128来进行划分,如果小于128字节,那么就会根据申请字节找到对应的哈希桶中去申请内存,如果没有就向内存池中申请内存。

static void * allocate(size_t n)
{
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;
    // 检测用户所需空间释放超过128(即是否为小块内存)
    if (n > (size_t) __MAX_BYTES) 
   {
        // 不是小块内存交由一级空间配置器处理
        return (malloc_alloc::allocate(n));
   }
    
    // 根据用户所需字节找到对应的桶号
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    
    // 如果该桶中没有内存块时,向该桶中补充空间
    if (result == 0)
   {
        // 将n向上对齐到8的整数被,保证向桶中补充内存块时,内存块一定是8的整数倍
        void *r = refill(ROUND_UP(n));
        return r;
   }
    
    // 维护桶中剩余内存块的链式关系
    *my_free_list = result -> free_list_link;
    return (result);
};

可以看到refill函数中,当哈希桶中没有了内存块的时候,这时候就需要向哈希桶中补充内存。我们会向内存池中一次性申请20个n字节的小内存,这时有两个策略

1)、如果我们只要了一块,那么我们就直接给用户。

2)、剩余的内存挂到哈希桶中

template <int inst>
void* __default_alloc_template<inst>::refill(size_t n)
{
    // 一次性向内存池索要20个n字节的小块内存
    int nobjs = 20;
    char * chunk = chunk_alloc(n, nobjs);
    
    obj ** 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);
    // 将第一块返回值用户,其他块连接在对应的桶中
      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);
}

二级空间配置器的回收

可以看到如果空间没有大于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 + FREELIST_INDEX(n);
q -> free_list_link = *my_free_list;
*my_free_list = q;
}

最后:空间配置器是将容器结合起来的,所有STL中的容器共用一份空间配置器,都可以采用这样的方式来申请内存,所以也可以理解为什么空间配置器都是静态的。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【vue】报错 Duplicate keys detected 解决方案

错误描述&#xff1a;Duplicate keys detected. This may cause an update error.错误直译&#xff1a;检测到重复的键。这可能会导致错误。错误原因&#xff1a;有相同父元素的多个子元素的v-for有相同的key值。 解决方法&#xff1a; return:{dataList:[{name:张三&#xf…

坚持刷题|二叉树的前、中、后序遍历(递归迭代)

文章目录 题目思考递归实现迭代实现前序遍历后序遍历中序遍历 在前、中、后序的迭代遍历中&#xff0c;为什么都采用栈来模拟递归&#xff0c;而非队列&#xff1f; Hello&#xff0c;大家好&#xff0c;我是阿月。坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;今天刷&…

excel给数据库初始化/旧数据处理(自动sql拼装)

思路&#xff1a; 首先导出数据到excel编写单条数据操作的sql利用excel CONCATENATE 函数自动生成&#xff0c;每一行数据的操作sql 小技巧:对于需要套娃的字段值&#xff0c;可以加一个临时列同样使用CONCATENATE函数进行sql拼装 案例&#xff1a; 1.临时列:CONCATENATE(C2, …

ROS方向第二次汇报(5)

文章目录 1.本方向内学习内容&#xff1a;1.1.自定义msg&#xff1a;1.1.1.定义msg文件&#xff1a;1.1.2.编辑配置文件&#xff1a; 1.2.自定义srv&#xff1a;1.2.1.定义srv文件&#xff1a;1.2.2.编辑配置文件&#xff1a; 1.3.服务通信案例实现&#xff1a;1.3.1.服务端实现…

回溯法:回溯法通用模版汇总以及模版应用

从一个问题开始 给定两个整数 n 和 k&#xff0c;返回 1 ... n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4] ] 很容易想到 用两个for循环就可以解决。 如果n为100&#xff0c;k为50呢&#xff0c;那就50层for循…

修改Vim编辑器的缩进和显示行数

一、Vim编辑器的缩进和显示行数 1.指令 sudo vi /etc/vim/vimrc2.插入内容 set tabstop4 set shiftwidth4 set nu 注意输入的格式&#xff0c;前后不要留空格 tabstop是输入按下tab缩进4个 shiftwidth是批量缩进4个 nu是显示行数

【blender烘焙】法线烘焙出现大面积结构丢失怎么办?blender烘焙vs八猴烘焙

用dcc烘焙法线是很常用的减面优化手段&#xff0c;很多建模的dcc自己也内置的烘焙的功能&#xff0c;像我自己在工作流中也偶尔用blender的烘焙做一下材质的整合优化&#xff0c;在质量要求不高的时候还算凑合可用。 问题描述 在前期的文章中飞燕2号建模&#xff0c;我就遇到…

Vue3+vite搭建基础架构(5)--- 使用vue-i18n

Vue3vite搭建基础架构&#xff08;5&#xff09;--- 使用vue-i18n 说明官方文档安装vue-i18n使用vue-i18n测试vue-i18n的国际化配置 说明 这里记录下自己在Vue3vite的项目使用vue-i18n做国际化语言的过程&#xff0c;不使用ts语法&#xff0c;方便以后直接使用。这里承接自己的…

Paper - 转角密度估计器 RDE (Rotamer Density Estimator) 算法

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/136002649 Paper: Rotamer density estimator is an unsupervised learner of the effect of mutations on protein-protein interaction 转角密…

公交最短距离-算法

题目 给定一个一维数组&#xff0c;其中每一个元素表示相邻公交站之间的距离&#xff0c;比如有四个公交站A,B,C,D&#xff0c;对应的距离数组为&#xff0c;1,2,3,4&#xff0c;如下图示 给定目标站X和Y&#xff0c;求他们之间最短的距离 解题 遍历一次整个数组&#xff0c;…

Docker搭建MySQL8主从复制

之前文章我们了解了面试官&#xff1a;说一说Binlog是怎么实现的&#xff0c;这里我们用Docker搭建主从复制环境。 docker安装主从MySQL 这里我们使用MySQL8.0.32版本&#xff1a; 主库配置 master.cnf //基础配置 [client] port3306 socket/var/run/mysqld/mysql.sock [m…

三分钟学懂C语言关键字——const

1&#xff0c;const修饰普通变量 const类型变量名常量; //类型&#xff1a;int char short 等等 类型const变量名常量; //举例&#xff1a;const int a5; int const a5;这两种写法表示a的值不能够改变 当我们直接改变const修饰的普通变量时&#xff0c;编译器会报…

Map和Set的封装

目录 一、底层原理 二、红黑树的节点 三、仿函数 四、迭代器 4.1、迭代器的定义&#xff1a; 4.2、*:解引用操作 4.3、->:成员访问操作符 4.4、!、 4.5、迭代器的&#xff1a; 4.6、迭代器的-- 五、Map 六、Set 七、红黑树源码 一、底层原理 我们要知道&#…

Docker 安装篇(CentOS)

Docker社区版 Docker从1.13版本之后采用时间线的方式作为版本号&#xff0c;分为社区版CE和企业版EE。 社区版是免费提供给个人开发者和小型团体使用的&#xff0c;企业版会提供额外的收费服务&#xff0c;比如经过官方测试认证过的基础设施、容器、插件等。 1、Docker 要求 C…

【Redis】整理

对于现代大型系统而言&#xff0c;缓存是一个绕不开的技术话题&#xff0c;一提到缓存我们很容易想到Redis。 Redis整理&#xff0c;供回顾参考

JVM系列——垃圾收集器Parrlel Scavenge、CMS、G1常用参数和使用场景

背景 当前在Java领域&#xff0c;JDK 8版本仍然享有广泛的使用&#xff0c;它支持了Parallel Scavenge、CMS和G1这几种垃圾收集器。因此&#xff0c;为了在业务应用中更加高效地进行开发和性能调优&#xff0c;我们需要对这些垃圾收集器的工作原理和特性有一个全面的理解和认识…

【Linux】vim的简单使用

我们知道在Windows下的VS2019是一个集成开发环境&#xff0c;也就是说&#xff0c;集编辑&#xff0c;编译&#xff0c;调试等功能都放在了一起&#xff1b;但是在Linux下&#xff0c;这些步骤都是分开的&#xff0c;我们这篇博客就来说一说vim这个编辑器&#xff0c;它只有编辑…

Android平台如何实现RTSP转GB28181

为什么要做GB28181设备接入侧&#xff1f; 实际上&#xff0c;在做Android平台GB28181设备接入模块的时候&#xff0c;我们已经有了非常好的技术积累&#xff0c;比如RTMP推送、轻量级RTSP服务、一对一互动模块、业内几乎最好的RTMP|RTSP低延迟播放器。 Android平台GB28181接…

clickhouse在MES中的应用-跟踪扫描

开发的MES&#xff0c;往往都要做生产执行跟踪扫描&#xff0c;这样会产生大量的扫描数据&#xff0c;用关系型数据库&#xff0c;很容易造成查询冲突的问题。 生产跟踪扫描就发生的密度是非常高的&#xff0c;每个零部件的加工过程&#xff0c;都要被记录下来&#xff0c;特别…

在Linux中对Nginx进行安全加固

准备工作 在IP为x.x.x.x的服务器上安装nginx&#xff0c;确保Linux系统为nginx环境。 检查nginx是否配置nginx账号锁定策略 配置nginx账号锁定策略&#xff0c;降低被攻击概率。 第一步&#xff0c;查看nginx的锁定状态。 命令&#xff1a;passwd -S nginx 若结果出现“P…
最新文章