STL空间配置器入门

STL简介
  STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。

谈及组件,那么我们就首先来简单谈下STL六大组件,其相关的设计模式使用,以及各组件之间的协作关系。

六大组件简单介绍
1. 空间配置器:内存池实现小块内存分配,对应到设计模式–单例模式(工具类,提供服务,一个程序只需要一个空间配置器即可),享元模式(小块内存统一由内存池进行管理)

2.迭代器:迭代器模式,模板方法

3.容器:STL的核心之一,其他组件围绕容器进行工作:迭代器提供访问方式,空间配置器提供容器内存分配,算法对容器中数据进行处理,仿函数伪算法提供具体的策略,类型萃取  实现对自定义类型内部类型提取。保证算法覆盖性。其中涉及到的设计模式:组合模式(树形结构),门面模式(外部接口提供),适配器模式(stack,queue通过deque适配得  到),建造者模式(不同类型树的建立过程)。

4.类型萃取:基于范型编程的内部类型解析,通过typename获取。可以获取迭代器内部类型value_type,Poter,Reference等。

5.仿函数:一种类似于函数指针的可回调机制,用于算法中的决策处理。涉及:策略模式,模板方法。

6适配器:STL中的stack,queue通过双端队列deque适配实现,map,set通过RB-Tree适配实现。涉及适配器模式。

 

STL空间配置器产生的缘由:

  在软件开发,程序设计中,我们不免因为程序需求,使用很多的小块内存(基本类型以及小内存的自定义类型)。在程序中动态申请,释放。

这个过程过程并不是一定能够控制好的,于是乎,

问题1:就出现了内存碎片问题。(ps外碎片问题)

问题2:一直在因为小块内存而进行内存申请,调用malloc,系统调用产生性能问题。

注:内碎片:因为内存对齐/访问效率(CPU取址次数)而产生 如 用户需要3字节,实际得到4或者8字节的问题,其中的碎片是浪费掉的。

  外碎片:系统中内存总量足够,但是不连续,所以无法分配给用户使用而产生的浪费。下边简单图解

  这两个问题解释清楚之后,就来谈STL空间配置器的实现细节了

实现策略

  用户申请空间大于128?

  yes:调用一级空间配置器
  no:调用二级空间配置器

大致实现为:

  二级空间配置由内存池以及伙伴系统:自由链表组成

  一级空间配置器直接封装malloc,free进行处理,增加了C++中的set_handler机制(这里其实也就是个略显牵强的装饰/适配模式了),增加内存分配时客户端可选处理机制。

可配置性:

  客户端可以通过宏__USE_MALLOC进行自定义选择是否使用二级空间配置器。

一级空间配置器就主要封装malloc,添加handler机制了,这里就不罗嗦了,相信各位都是可以通过源码了解到的

 

Trace使用

  对于内存池的内部实现过程共还是比较复杂的,虽然代码量,函数比较简单。但是调用过程可能比较复杂。这时,如果我们选择debug调试,过程会相当的繁琐,需要仔细记录调用堆栈过程以及数据流向,逻辑变更等。对于楼主这种水货来说,估计完事就要苦了。

  所以,就使用Trace进行跟踪,打印数据流向,逻辑走向,文件,函数,方法,行位置。那么我们就能根据这个记录进行程序的排错以及调优了。

具体Trace简单如下

#pragma once

#define ___TRACE(...) fprintf(fout, "file[%s]line[%u]func[%s]::",__FILE__,__LINE__,__func__);\
fprintf(fout,__VA_ARGS__)

没错,就是这么简单,利用宏打印文件,行,函数位置,然后利用可变参数列表方式接收代码中具体位置的记录跟踪。

如下是代码摘取的Alloc中的跟中。

static void *Allocate(size_t n)
    {
        ___TRACE("__MallocAllocTemplate to get  n = %u\n",n);
        void *result = malloc(n);
        if (0 == result)
        {
            result = OomMalloc(n);
        }
        return result;
    }

1.仔细探究源码之后,你一定会发现一个问题,
  貌似二级空间配置器中的空间重头到尾都没看到他归还给系统。那么问题就是,内存池空间何时释放?

对于这个问题,在回头浏览一下源码及结构图,你就会发现

  大于128的内存,客户程序Deallocate之后会调free释放掉,归还给了系统。

  但是呢……………

  内存池中获取的空间,最终,假定用户都调用Dealloc释放调了,那么他们又在哪里呢?

    没有还给系统,没有在内存池,在自由链表中。

Got it:程序中不曾释放,只是在自由链表中,且配置器的所有方法,成员都是静态的,那么他们就是存放在静态区。释放时机就是程序结束。

2.如果需要释放,那么应该怎么处理呢?
  因为真正可以在程序运行中就归还系统的只有自由链表中的未使用值,但是他们并不一定是连续的(用户申请空间,释放空间顺序的不可控制性),所以想要在合适时间(eg一级配置器的handler中释放,或者设置各阀值,分配空间量到达时处理),就必须保证释放的空间要是连续的。保证连续的方案就是:跟踪分配释放过程,记录节点信心。释放时,仅释放连续的大块。

3.关于STL空间配置器的效率考究
  既然已经存在,而又被广泛使用,那么,整体的效率,以及和STL内部容器之间的使用配合还是没问题的。

我们考虑几种情况:
  a. 用户只需要无限的char类型空间,然而配置器中却对齐到8,于是乎,整个程序中就会有7/8的空间浪费。

  b.对于假定用户申请N次8空间,将系统资源耗到一定程度,然后全部释放了,自由链表中的空间都是连续的。却没有释放。

    但是:用户需要申请大于8的空间时,却依旧没有空间可用。

总之:这个问题就是,空间可能全部积攒在小块自由链表中,却没有用户可用的。这就尴尬了。

STL空间配置器主要分三个文件实现:
(1)<stl_construct.h> :这里定义了全局函数construct()和destroy(),负责对象的构造和析构。

(2)<stl_alloc.h>:文件中定义了一、二两级配置器,彼此合作,配置器名为alloc。

(3)<stl_uninitialized.h>:这里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,他们也都隶属于STL标准规范。

首先需要说明的是二级空间配置器是由一个内存池和自由链表配合实现的。

 

srartFree就相当于水位线的一种东西,它标志着内存池的大小。

自由链表中其实是一个大小为16的指针数组,间隔为8的倍数。各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字节的小额区块。在每个下标下挂着一个链表,把同样大小的内存块链接在一起。此处特别像哈希桶。
自由链表结构:
这个结构可以看做是从一个内存块中抠出4个字节大小来,当这个内存块空闲时,它存储了下个空闲块,当这个内存块交付给用户时,它存储的时用户的数据。因此,allocator中的空闲块链表可以表示成:
obj* free_list[16];

obj* 是4个字节那么大,但是大部分内存块大于4。我们想要做的只是将一块块内存链接起来,我们不用看到内存里所有的东西,所以我们可以只用强转为obj*就可以实现大内存块的链接。

二级空间配置器是为频繁分配小内存而生的一种算法。其实就是消除一级空间配置器的外碎片问题。

ChunkAlloc要做的就是去找操作系统要内存,依次性要20个,但是我们要考虑很多情况:

内存池里有足够20块大的内存
内存池里有小于20块大于等于1块的内存大小
内存池里1块内存那么大都没有
STL是这样做的: 如果有足够的内存,那么一次性就给20块,返回第一块给用户,其余的挂在自由链表上。
只有一块或者多块,返回一块给用户。
没有内存了,找操作系统要。
操作系统没有了,启用最后一根救命稻草,调用一级空间配置器,通过句柄函数释放内存,分配内存。

这个就是二级空间配置器的主要逻辑结构。

还有要说明的几点就是:
空间配置器里所有的成员都是静态的。是为了在外面通过作用域就可以调用,而不需要构造对象。
空间配置器可以使用于大部分的数据结构,如List,vector等。
对于自由链表的初始化时特别容易错的。

template<bool threads, int inst>
typename DefaultAllocTemplate<threads, inst>::obj* volatile  
        DefaultAllocTemplate<threads, inst>::FreeList[FREELISTSIZE] = { 0 };

注意到typename了吗。它就为了完成一个功能,到苏编译器DefaultAllocTemplate<threads, inst>是一个类型,不然会出现错误

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

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

相关文章

如何在免费版 pycharm 中使用 github copilot (chatGPT)?

起因 在 vscode 中使用了 github copilot 以后&#xff0c;感觉这个人工智能还不错。 但 vscode 对于 python 项目调试并不是特别方便&#xff0c;所以想在 Pycharm 中也能使用同一个 github 账号&#xff0c;用上 copilot 的功能。 不需要等待&#xff0c;安装即用&#xff…

C++和Lua交互总结

C和Lua交互总结 Chapter1. C和Lua交互总结一、Lua与C的交互机制——Lua堆栈二、堆栈的操作三、C 调用 Lua1&#xff09;C获取Lua值2&#xff09;C调用Lua函数示例&#xff1a; 四、Lua 调用 C包装C函数 最后总结一下 Chapter1. C和Lua交互总结 原文链接&#xff1a;https://bl…

数据结构: 线性表(带头双向循环链表实现)

之前一章学习了单链表的相关操作, 但是单链表的限制却很多, 比如不能倒序扫描链表, 解决方法是在数据结构上附加一个域, 使它包含指向前一个单元的指针即可. 那么怎么定义数据结构呢? 首先我们先了解以下链表的分类 1. 链表的分类 链表的结构非常多样, 以下情况组合起来就有…

【图论】无向图连通性(tarjan算法)

割边&#xff1a;dfn[u]<low[v] 割点&#xff1a;dfn[u]<low[v] (若为根节点&#xff0c;要有两个v这样的点) 一.知识点&#xff1a; 1.连通&#xff1a; 在图论中&#xff0c;连通性是指一个无向图中的任意两个顶点之间存在路径。如果对于图中的任意两个顶点 u 和 v&…

06 HTTP(下)

06 HTTP&#xff08;下&#xff09; 介绍服务器如何响应请求报文&#xff0c;并将该报文发送给浏览器端。介绍一些基础API&#xff0c;然后结合流程图和代码对服务器响应请求报文进行详解。 基础API部分&#xff0c;介绍stat、mmap、iovec、writev。 流程图部分&#xff0c;描…

写材料使用恰当的词汇和专业术语,不要使用生僻或不恰当的词汇

注意使用恰当的词汇和专业术语是公文写作中的关键&#xff0c;不要使用过于生僻或不恰当的词汇。 首先&#xff0c;在选择词汇和专业术语时&#xff0c;需要了解公文所涉及的领域和专业知识。对于不同领域和专业的公文&#xff0c;需要选择恰当的词汇和术语&#xff0c;以确保公…

Akuity Certified ArgoCD课程学习与认证

今天是「DevOps云学堂」与你共同进步的第 48天 第⑦期DevOps实战训练营 7月15日已开营 实践环境升级基于K8s和ArgoCD 本文主要分享&#xff0c;如何免费地参与由Akuity Academy提供的ArgoCD GitOps 培训课程并取得认证证书。 目前Akuity Academy只发布了Introduction to Contin…

王道计网 第四章笔记

4.1 生活在网络层的“工人”是路由器,他负责各种异构网络的连接,但是因为他只生活在前三层所以从网络层之上的东西他不能管理,所以网路层之上的数据对于路由器来说必须是相同的、透明的。 常见的网络层协议有IP 和 ICMPTCP IP传输层协议FTP应用层协议一句话区分IP和MAC地址…

GO语言的垃圾回收机制

内存垃圾的产生 程序在内存上被分为堆区、栈区、全局数据区、代码段、数据区五个部分。对于C等早期编程语言栈上的内存回由编译器负责管理回收&#xff0c;而堆上的内存空间需要编程人员负责申请和释放。在Go中栈上内存仍由编译器负责管理回收&#xff0c;而堆上的内存由编译器…

snap xxx has “install-snap“ change in progress

error description * 系重复安装&#xff0c;进程冲突 solution 展示snap的改变 然后sudo snap abort 22即可终止该进程 之后重新运行install command&#xff5e;&#xff5e; PS: ubuntu有时候加载不出来&#xff0c;执行resolvectl flush-caches&#xff0c;清除dns缓存…

ChatGPT即将取代程序员

W...Y的主页 相信ChatGPT大家已经都不陌生&#xff0c;我们经常会在工作和学习中应用。但是ChatGPT的发展速度飞快。功能也越来越全面。ChatGPT的文章也是层次不穷的出现&#xff0c;ChatGPT即将取代程序员的消息也铺天盖地。那ChatGPT真的会取代程序员吗&#xff1f;我们是否…

【深度学习_TensorFlow】梯度下降

写在前面 一直不太理解梯度下降算法是什么意思&#xff0c;今天我们就解开它神秘的面纱 写在中间 线性回归方程 如果要求出一条直线&#xff0c;我们只需知道直线上的两个不重合的点&#xff0c;就可以通过解方程组来求出直线 但是&#xff0c;如果我们选取的这两个点不在直…

GD32F103VE外部中断

GD32F103VE外部中断线线0~15&#xff0c;对应外部IO口的输入中断。它有7个中断向量&#xff0c;外部中断线0 ~ 4分别对应EXTI0_IRQn ~ EXTI4_IRQn中断向量&#xff1b;外部中断线 5 ~ 9 共用一个 EXTI9_5_IRQn中断向量&#xff1b;外部中断线10~15 共用一个 EXTI15_10_IRQn中断…

MySQL数据库:表的约束

表的约束&#xff0c;实质上就是用数据类型去约束字段&#xff0c;但是数据类型的约束手法很单一&#xff0c;比如&#xff0c;我们在设置身份证号这个字段&#xff0c;数据类型唯一起的约束是它属于char类型或者varchar类型&#xff0c;不能是浮点型也不能是日期时间类型&…

.net 6 efcore一个model映射到多张表(非使用IEntityTypeConfiguration)

现在有两张表&#xff0c;结构一模一样&#xff0c;我又不想创建两个一模一样的model&#xff0c;就想一个model映射到两张表 废话不多说直接上代码 安装依赖包 创建model namespace oneModelMultiTable.Model {public class Test{public int id { get; set; }public string…

Linux服务器大量日志如何快速定位

Linux服务器大量日志如何快速定位 在生产环境&#xff0c;定位问题&#xff0c;经常会遇到日志文件特别多的情况&#xff0c;经常会遇到日志比较难拿的情况&#xff0c;所以有什么方法可以快速拿日志&#xff1f;除了在代码里很好的打印关键日志信息外&#xff0c;也需要掌握L…

RabbitMQ 教程 | 第10章 网络分区

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

第20节 R语言医学分析:某保险医疗事故赔偿因素分析

文章目录 某保险医疗事故赔偿因素分析源码源文件下载某保险医疗事故赔偿因素分析 我们分析数据集“诉讼”的第一个方法是确定样本数量、变量类型、缩放/编码约定(如果有)用于验证数据清理。 接下来,数据集看起来很干净,没有缺失值,并且对于分类变量,将编码约定替换为实际…

智慧工地云平台源码,基于微服务+Java+Spring Cloud +UniApp +MySql开发

智慧工地可视化系统利用物联网、人工智能、云计算、大数据、移动互联网等新一代信息技术&#xff0c;通过工地中台、三维建模服务、视频AI分析服务等技术支撑&#xff0c;实现智慧工地高精度动态仿真&#xff0c;趋势分析、预测、模拟&#xff0c;建设智能化、标准化的智慧工地…

LeetCode151.反转字符串中的单词

151.反转字符串中的单词 目录 151.反转字符串中的单词题目描述解法一&#xff1a;调用API解法二&#xff1a;原生函数编写 题目描述 给你一个字符串s&#xff0c;请你反转字符串中单词的顺序。 单词是由非空格字符组成的字符串&#xff0c;s中使用至少一个空格将字符串中的单…