【C++】空间配置器
空间配置器,听起来高大上,那它到底是什么东西呢?
1.什么是空间配置器?
空间配置器是STL
源码中实现的一个小灶,用来应对STL
容器频繁申请小块内存空间的问题。他算是一个小型的内存池,以提升STL
容器在空间申请方面的效率
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;# endifresult = *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
哈希桶以8字节对齐,分为了16个桶。最开始的时候,free_list
哈希桶是空的
当我们需要小于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. */
};
当我们再次需要这部分大小的空间的时候,只需要将哈希桶头指针的指向直接修改,就能链接上下一个空间,并将之前头指针指向的空间拿去用
用的时候也不用管之前在此处存放的指针,毕竟下一次放回来的时候,二级空间配置器会来处理它的
内存池空间不够用了咋办?
之前提到了,二级空间配置器里面有一个小内存池。要是这个内存池用完了,要咋整呢?
二级空间配置器想出来了一种很骚的做法:
- 假设当前我们需要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默认模板参数
这里默认传的便是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的
有啥问题可以在评论提出哦
相关文章:

【C++】空间配置器
空间配置器,听起来高大上,那它到底是什么东西呢? 1.什么是空间配置器? 空间配置器是STL源码中实现的一个小灶,用来应对STL容器频繁申请小块内存空间的问题。他算是一个小型的内存池,以提升STL容器在空间申…...

nginx的介绍及源码安装
文章目录前言一、nginx介绍二、nginx应用场合三、nginx的源码安装过程1.下载源码包2.安装依赖性-安装nginx-创建软连接-启动服务-关闭服务3.创建nginx服务启动脚本4.本实验---纯代码过程前言 高可用:高可用(High availability,缩写为 HA),是指系统无中断地执行其功…...

通过openssl生成pfx证书
通过centos7上自带的openssl工具来生成。首先创建一个pfxcert目录。然后进入此目录。 1.生成.key文件(内含被加密后的私钥),要求输入一个自定义的密码 [rootlocalhost cert]# openssl genrsa -des3 -out server.key 2048 Generating RSA priv…...

华为OD机试真题Python实现【敏感字段加密】真题+解题思路+代码(20222023)
敏感字段加密 题目 给定一个由多个命令字组成的命令字符串; 字符串长度小于等于127字节,只包含大小写字母,数字,下划线和偶数个双引号命令字之间以一个或多个下划线_进行分割可以通过两个双引号""来标识包含下划线_的命令字或空命令字(仅包含两个双引号的命令字…...

我的 System Verilog 学习记录(1)
引言 技多不压身,准备开始学一些 System Verilog 的东西,充实一下自己,这个专栏的博客就记录学习、找资源的一个过程,希望可以给后来者一些借鉴吧,IC找工作的都加把油! 本文是准备先简单介绍一下环境搭建…...

金三银四,我不允许你们不知道这些软件测试面试题
01、您所熟悉的测试用例设计方法都有哪些?请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。 答:有黑盒和白盒两种测试种类,黑盒有等价类划分法,边界分析法,因果图法和错误猜测法。白盒有逻辑覆盖法&…...

【UnityEditor】Unity将Multiple Sprite分割成多张png小图
如题,代码如下 using UnityEngine; using UnityEditor; using System.IO;public class SplitTexture {[MenuItem("ExtraTools/SplitTexture")]static void DoSplitTexture(){// 获取所选图片Texture2D selectedImg Selection.activeObject as Texture2D…...

独立搭建 handle server
本节主要介绍,如何搭建一个与 GHR隔离的 handle sever,不与外界有任何连通。 下载文件 访问地址下载最新版:http://www.handle.net/download_hnr.html 这里以 9.3.0 版本作为讲解 解压服务端,解压客户端 # 解压 tar -xzvf handle-9.3.0-distribution.tar.gz# 到目录下 …...

记一次KindEditor表格修改无效问题
项目说明 项目是由UmiJS创建的(ReactAnt Design4.2),项目需求是富文本编辑器录入多样内容,可供查看。 通过各方探索以及客户的沟通,选定了KindEditor编辑器,通过iframe嵌入。但仍有很多不符合要求的地方,所以要进行很…...

一种图片展示的完美方案,图片展示,object-fill
通常一般的处理 <style>.img-container {width: 300px;height: 200px;background: #f60;}img {width: 100%;height: 100%;}</style> </head> <body><div class"img-container"><img src"./行道树.png" alt""&g…...

社科院杜兰金融管理硕士——考研初试成绩已出,关于分数“6线”你有了解吗
多地公布了2023考研初试成绩查询时间,部分省份今日就能查询到考研初试成绩,考研学子们此刻的心情应该是很忐忑吧,关于分数的“6线”你都知道有哪些吗?我们跟随社科院杜兰金融管理硕士项目一起去了解一下。1.国家线教育部依据硕士生…...

Talk | 清华大学交叉信息研究院助理教授杜韬:利用计算方法探究流固耦合
本期为TechBeat人工智能社区第474期线上Talk! 北京时间2月15日(周三)20:00,清华大学交叉信息研究院助理教授——杜韬的Talk将准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “利用计算方法探究流固耦合”,届时将介绍流固…...

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

数据分析-深度学习 NLP Day2关键词提取案例
训练一个关键词提取算法需要以下几个步骤:1)加载已有的文档数据集;2)加载停用词表;3)对数据集中的文档进行分词;4)根据停用词表,过滤干扰词;5)根据…...

LeetCode题解:938. 二叉搜索树的范围和,BFS,JavaScript,详细注释
原题链接: https://leetcode.cn/problems/range-sum-of-bst/ 解题思路: 对于二叉搜索树的任意节点,左子树的所有节点值都小于它的值,右子树的所有节点值都小于它的值。使用队列进行BFS搜索,如果当前节点的值小于low&…...

istio初步了解
istio 控制平面: Pilot:管理和配置部署在特定istio服务网格中的所有sidecar代理实例,管理sidecar代理之间的路由流量规则,并配置故障恢复功能,如超时、重试、熔断。 Citadel:istio中负责身份认证和证书管…...

【模板】用HTML编写邮件正文 | 各大邮箱几乎都会过滤css样式、js脚本等效果,如何用基础HTML编写?
用HTML编写邮件正文 文档 编码格式utf-8(使用记事本或其他工具打开,在文件->另存为,编缉选择UTF-8格式) 文档大小在15kb以内 样式 页面宽度:600px~800px 尽量用特殊元素以及元素属性代替样式 样式全部写为内联样式…...

华为云计算之双活容灾
双活(HyperMetro)本地双活:距离≤10km同城双活:距离>10km没有主备之分,只有本端数据中心和远端数据中心。当一个数据中心的设备故障或数据中心故障,业务会自动切换到另一个数据中心继续运行&…...

ASEMI高压MOS管ASE20N65SE体积,ASE20N65SE大小
编辑-Z ASEMI高压MOS管ASE20N65SE参数: 型号:ASE20N65SE 漏极-源极电压(VDS):650V 栅源电压(VGS):30V 漏极电流(ID):20A 功耗(P…...

resp连接redis服务器
修改redis的配置文件使得windows的图形界面客户端可以连接redis服务器 resp安装好以后,可以在linux端打开redis.conf中做以下操作,使得windows的图形界面客户端可以连接redis服务器 方法一: 1,在redis.conf文件中添加bind 在文件…...

七天实现一个分布式缓存
目录教程来源目的思路缓存淘汰(失效)算法:FIFO,LFU 和 LRUFIFO(First In First Out)LFU(Least Frequently Used)LRU(Least Recently Used)实现Lru查找功能删除新增/修改测试单机并发缓存主体结构 Group回调 GetterGroup 的定义Group 的 Get 方法HTTP 服务…...

电子招标采购系统源码功能清单
一、立项管理 1、招标立项申请 功能点:招标类项目立项申请入口,用户可以保存为草稿,提交。 2、非招标立项申请 功能点:非招标立项申请入口、用户可以保存为草稿、提交。 3、采购立项列表 功能点:对草稿进行编辑&#x…...

mysql数据库基础知识
一.mysql基本命令 1.基础常用命令 mysql -uroot -p密码;(也可以不带密码,之后输入) 本地登录 mysql -h 登录ip -p 端口(通常3306) -uroot -p密码; 远程登录 desc 表名;查看表的各个字段的属性,以及自增键 mysqldump -u用户 -p 数据库名 >…...

CAN总线通信
CAN总线通信 CAN 是控制器局域网络(Controller Area Network) 的缩写,是 ISO 国际标准化的串行通信协议。 CAN是半双工通信 CAN总线特点 (1) 多主控制 在总线空闲时,所有的单元都可开始发送消息(多主控制…...

MATLAB/Simulink 通信原理及仿真学习(二)
文章目录MATLAB/Simulink 通信原理及仿真学习(二)simulink仿真常用的Simulink库1. 信号源模块库2. 数序运算模块3. 信号输出模块库4.仿真搭建5.搭建自己的库6.S-函数编写MATLAB/Simulink 通信原理及仿真学习(二) simulink仿真 交…...

CentOS7 防火墙(firewall)的操作命令
CentOS7 防火墙(firewall)的操作命令 安装:yum install firewalld 1、firewalld的基本使用 启动: systemctl start firewalld 查看状态: systemctl status firewalld 禁用,禁止开机启动: s…...

文献工具汇总:论文查找、文献管理、文献翻译
科研人员论文哪里找?文献如何管理?本文给推荐一些提高论文阅读写作效率的一些资料,包括查找论文、文献管理、文献翻译等方面。 一、查找文献 PMC(Pubmed Cenral) Pubmed官方系统中,将免费的全文集中在此,…...

SQL零基础入门学习(三)
SQL零基础入门学习(二) SQL WHERE 子句 WHERE 子句用于提取那些满足指定条件的记录。 SQL WHERE 语法 SELECT column1, column2, ... FROM table_name WHERE condition;参数说明: column1, column2, …:要选择的字段名称&…...

苹果手机如何快速的直接从相册里面的图片提取文字?
//在线工具地址https://ocr.bytedance.zj.cn/image/ImageText在当今信息爆炸的时代,图文并茂已经成为了一个广告宣传的常用方式。然而,图片中的文字信息往往难以获取,尤其对于那些需要快速获取信息的人们来说,阅读图片中的文字会是…...

【go】函数调用
程序中编写的函数在编译阶段会被编译成一段段的指令存放在可执行文件中,在程序运行阶段这些内存会加载到虚拟地址空间的代码段。 当函数A调用了函数B的时候,对应的会生成一条call指令,程序在运行到call指令时就会跳转到对应的B函数的代码段的…...