C++学习之空间配置器--------(二级空间配置器)

2023-11-01

出现的原因

原因:
由于我们申请的空间有时会太小,而频繁的去申请这样的小空间就会造成太多小额区块造成的内存碎片。对于这些小额区块所带来的不仅仅是内存碎片,更是配置时的额外负担。而额外的负担也是一个大问题,因为我们还要靠这一部分去管理我们所使用的内存空间,如果我们所需要的空间越小,那么相比于开辟的空间来说,额外负担越大,就越显得浪费。

二级空间配置器的原理

1.解决问题:对于上述的问题,二级空间配置器是这样解决的:
①:首先,它会用128字节的大小去划分我们申请的内存是使用一级空间配置器和二级空间配置的,如果超过申请的内存128字节,那么就用一级空间配置器去申请空间,如果不大于128字节,就用二级空间配置器来申请内存。

②:其次,它是以内存池的方式来管理的。

③:内存池:而内存池的管理方式为哈希桶加自由链表,每次申请节点的时候,我们在对应哈希桶下的自由链表申请一块大的内存空间,然后用户需要的我们给其用,这样等第二次申请内存的时候,直接从内存池中去拿即可,这样就避免了对于小空间不断的对内存进行申请的问题了,提高了运行效率。当然,如果当用户释放了自己在内存池中拿的空间,那么这个空间还是会被返回来,等下一次分配的时候,就可以用这块内存了。

④:节点的选择:申请内存小,那么就会显得管理内存的额外负担是大的,这个问题是这样解决的---------申请节点的时候,我们使用的是联合体的结构,这样直接提高了我们对内存的利用率,其底层的union实现如下:

union obj
{
   union obj* free_list_link;//指向下一个节点
   char client_data[1];//用户存储的数据
}

⑤:自由链表和哈希桶的管理方式:

SGI二级空间配置器会将用户需要的内存会自动上调至8的倍数,这是为了方便去管理数据,所以对于128个字节一共维护了16个自由链表如下:
在这里插入图片描述
而当我们对应申请内存的时候,他会对申请的内存进行上调,然后上调完找到对应的哈希桶下的自由链表,然后去取数据或者去填充完成后再去取数据。
上调函数的底层实现如下:

static size_t ROUND_UP(size_t bytes)
{
   return(((bytes)+_ALIGN-1) & ~(_ALIGN-1));
}

其中bytes是用户所需要的数据,而_ALIGN为8,因为是通过8的倍数来去申请内存的。

例如:我们需要用9个内存,那么上调函数会给我们上调到16个字节,然后在对应16个字节下的自由链表进行使用。

2.自由链表对空间如何开辟和使用:
①:开辟
对于用户需要取申请空间的时候,二级空间配置器的上调函数会将将其上调至8的倍数,然后再通过对应的哈希函数找到其哈希桶的下标,然后对其进行开辟空间。
首先,哈希函数的定义如下:

static size_t FREELIST_INDEX(size_t bytes)
{
   return ((bytes)+_ALIGN-1)/_ALIGN-1);
}

其中,bytes是我们上调后的数据,而_ALIGN是8。

其次,对于其内存的开辟,他不会开辟的只有需要的大小,他会一次性开辟大的内存,对于SGI的二级内存配置他会一次性开辟用户所需要的内存的20倍。然后将用户所需要的第一个节点返回给用户去使用,而后的19个节点会交给哈希桶对应的自由链表去管理。
例如下图:
在这里插入图片描述
他会让自由链表直接管理第二块内存了,第一块内存给客户去使用。
而当客户释放了对应的那块内存,自然会让自由链表指向第二个节的哪个指针指回来,继续管理释放后的加上以前管理的全部内存,等待用户的下一次使用。

②:使用:
首先对于使用内存他会有三个情况:

  1. 情况一:如果我们需要用的内存(这个内存是20倍后的),在内存池中是存在的,可以直接拿来用的,那么就直接拿来用。
  2. 情况二:如果我们需要的已经20倍后的内存在原有的内存池中是没有的,但是对于用户目前需要使用的内存是存在的,那么就先拿来用。
  3. 情况三:我们需要内存的最低要求都没有,这个时候我们就需要对底层去申请空间了,但是在申请内存之前,我们会将内存池中可用的内存先全部编起来,然后去申请更大块的内存(此时可能会出现意外,如果出现意外,他会将内存池中的可以释放的内存先释放,然后去在递归调用),(如果在上一步的意外之后,还是没有得到解决,就只能通过一级空间配置器去解决了)然后再递归调用。

二级空间配置器的底层代码解析

#include<iostream>
#include<new>
#include<thread>
using namespace std;
/二级配置器的模拟实现
enum{_ALIGN = 8};//代表小区块的上调边界
enum{_MAX_BYTES = 128};//代表小区块的上限
enum{ _NFREELISTS = _MAX_BYTES / _ALIGN };//代表自由链表的数量
template<bool threads,int inst>
class _default_alloc_template
{
public:
	static void* allocate(size_t n);//申请空间函数
	static void  deallocate(void* p, size_t n);//释放空间函数
	static void* reallocate(void* p, size_t old_sz, size_t new_sz);//空间不够,需要添加的申请空间函数

private:
	static size_t ROUND_UP(size_t bytes)//上调函数:申请空间如果不是8的倍数,那么就上调到8的倍数。
	{                                   //主要是为了refill函数的工作。
		return (bytes + (_ALIGN - 1) & ~(_AlIGN - 1));
	}
private:
	union obj//自由链表的节点构造,用了联合体也是大大的提高了内存的利用率。
	{
		union obj* free_list_link;
		char client_data[1];//用户需要存的数据
	};
private:
	static obj* 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);//从内存池中取空间。

private:
	static char* start_free;//内存池起始位置
	static char* end_free;//内存池终止位置
	static size_t heap_size;//一个哈希桶下的自由链表的大小
};
//初始化阶段,将指针和自由链表开始都置为空。
template<bool threads, int inst>
char* _default_alloc_template<threads, inst>::start_free = 0;
template<bool threads, int inst>
char* _default_alloc_template<threads, inst>::end_free = 0;
template<bool threads, int inst>
size_t _default_alloc_template<threads, inst>::heap_size = 0;
template<bool threads, int inst>
obj* _default_alloc_template<threads, inst>::free_list[_NFREELISTS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
//申请空间
template<bool threads, int inst>
void* _default_alloc_template<threads, inst>::allocate(size_t n)
{
	if (n > 128)//如果申请的字节数大于128,那么就交给一级空间配置器去操作。
		return (malloc_alloc::allocate(n));
	obj** my_free_list;
	obj* result;
	
	my_free_list = free_list + freelist_index(n);//找到要使用的哈希桶
	result = *my_free_list;

	if (result == 0)//此时这个哈希桶还未初始化
	{
		void* r = refill(ROUND_UP(n));
		return r;
	}
	*my_free_list = result->free_list_link;//修改哈希桶的指针,让其指向未被利用的空间。
	return (result);
}
template<bool threads, int inst>
void* _default_alloc_template<threads, inst>::refill(size_t n)
{
	int nobjs = 20;//这是我们要开辟多少个需要的字节个数的倍数。
	char* chunk = chunk_alloc(n, nobjs);//这个函数主要是帮助我们去获取空间,获取我们需要申请的总空间大小。
	obj** my_free_list;//自由链表
	obj* result;//返回的空间大小
	obj* current_obj;//当前的节点
	obj* next_obj;//下一个节点
	int i;
	if (nobjs == 1)//我们只需要一个节点,那么就直接将其返回即可。(就可以不用调整free_list了)
		return (chunk);
	//申请节点过多,需要调整free_list
	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)//当此时i等于申请的最后一个节点的时候,那么就可以让其下一个节点为空,因为就申请了这么多小节点
		{
			current_obj->free_list_link = 0;
			break;
		}
		else
		{
			current_obj->free_list_link = next_obj;//那就让他们再连接起来。
		}
	}
	return (result);
}
template<bool threads, int inst>
char* _default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
	char* result;//返回的内存
	size_t total_bytes = size* nobjs;
	size_t left_bytes = end_free - start_free;//内存池还剩多少内存。

	if (left_bytes >= total_bytes)//如果剩余的内存量大于或者等于需要的申请的内存,那么直接拿就完了。
	{
		result = start_free;
		start_free += total_bytes;
		return(result);
	}
	else if (left_bytes >= size)//此时剩余内存量不够申请的内存,但是可以满足正准备需要的,那么还可以先拿出来这部分。
	{
		nobjs = left_bytes / size;//修改nobjs,此时能申请几个就申请几个,要求不那么严格了,先满足当下需要,其他再说。
		total_bytes = size*nobjs;
		result = start_free;
		start_free += total_bytes;
		return(result);
	}
	else//此时就是大问题了,内存连最基本的需求都不够了,这下得去申请空间了。
	{
		size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);//反正此时都摊牌了,给底层要空间了,那么必须得多要。
		if (bytes_left > 0)//但是在要之前,先看看内存池中到底还有没有内存,如果有,先垫着。
		{
			obj** my_free_list = free_list + freelist_index(bytes_left);
			((obj*)start_free)->free_list_link = *my_free_list;
			*my_free_list = (obj*)start_free;
		}
		//然后去malloc申请空间
		start_free = (char*)malloc(bytes_to_get);
		if (start_free == 0)//申请空间失败
		{
			int i;
			obj** my_free_list;
			obj* p;
			for (i = size; i <= _MAX_BYTES; i += _ALIGN)//那就不去自己申请了,先保住我们自己所剩余的内存,拿去用。
			{
				my_free_list = free_list + freelist_index(i);
				p = *my_free_list;
				if (p != 0)//如果p所指的自由链表下面还有空间,那么就直接拿来用。
				{
					*my_free_list = p->free_list_link;
					start_free = (char*)p;
					end_free = start_free + i;
					return(chunk_alloc(size, nobjs));//递归调用,就是看拿来用的内存如果够了那么就可以直接先用着。
				}
			}
			end_free = 0;
			start_free = (char*)malloc_alloc::allocate(bytes_to_get);//如果不够,那就用一级内存配置器的行为去申请空间,此时有内存申请失败的处理,安全些。
		}
		heap _size += bytes_to_get;
		end_free = start_free + bytes_to_get;
		return(chunk_alloc(size, nobjs));
	}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++学习之空间配置器--------(二级空间配置器) 的相关文章

随机推荐

  • JSP分页处理

    作为代码萌新的我今天尝试做了一个JSP的分页处理 如果有什么不好的地方请在评论区留下建议 废话不多说 先看我做的效果图 我在查询数据的时候使用了异步AJAX 在数据绑定的时候使用的是vue 因为我感觉vue用来绑定数据更方便一点 分页插件使
  • java 实现SMS短信发送

    准备工作 注册账号 http sms webchinese cn reg shtml 查看短信密钥 代码实现 package com activiti test import org apache commons httpclient He
  • Eureka的黑白名单过滤机制(Eureka的注册黑白名单)

    参考链接 Eureka的注册黑白名单 不过这篇博文只提供了实现思路和大致 我这边帮忙把完整代码贴出来 通过springboot的autoconfigure实现 大致思路是用自己定义的Eureka注册包装类替换原来的Eureka注册类 当Eu
  • SpaceX预计到2022年Starlink用户将达到2000万,但最终达到了100万

    SpaceX的Starlink部门还没有接近实现客户和收入的预测 该公司在建立卫星网络之前与投资者分享了这一点华尔街日报报道今天出版 据报道 2015年的一份题为 SpaceX用来从投资者那里筹集资金 的报告预计 到2022年 Starli
  • C++ String去除头尾空格 实现trim()方法

    虽然C 11的标准库中并没有提供trim 方法 但我们可以使用string的find first not of 和find last not of方法实现trim include
  • centos linux介绍,关于CentOS使用的简介

    CentOS对大家来说已经很熟悉 什么是CentOS呢 CentOS Community ENTerprise Operating System 是Linux发行版之一 它是来自于Red Hat Enterprise Linux依照开放源代
  • BUUCTF WEB 强网杯 2019 随便注

    1 题目 2 解题 2 1 尝试点提交 url变为 http b61f33a4 644b 402a 9da5 4bdf8043f954 node4 buuoj cn inject 1 可知传入的参数名为 inject 参数为1 2 2 尝试
  • vector中reserve与resize区别

    vector中reserve与resize区别 一 基本概念 1 capacity 指容器在分配新的存储空间之前能存储的元素总数 2 size 指当前容器所存储的元素个数 二 reserve与resize 1 区别 1 reserve 只修
  • 如何计算当前进程的CPU占用率

    由于测试一个解码器的项目 很长时间都在反复进行domain knowledge的学习 再加上自己是一个测试新手 对于测试代码撰写啥的还很是生嫩 前一阵被要求在性能测试中 最好在测试时能够计算出解码进程的CPU占用率 做为我们参考的一种性能参
  • w10 没有internet信息服务器,win10找不到“internet信息服务(IIS)管理器”怎么办...

    用户在搭建开发环境的时候 找了很久没发现 internet信息服务 IIS 管理器 那么 internet信息服务 IIS 管理器 去哪里了 如果你在win10找不到 internet信息服务 IIS 管理器 不要着急 在这里跟大家分享下具
  • shell脚本基础知识-shell中的特殊符号

    1 代表零个或多个字符或数字 test后面可以没有任何字符 也可以有多个字符 总之有或没有都能匹配出来 2 只代表一个任意的字符 不管是数字还是字母 只要是一个都能匹配出来 3 这个符号在linux中表示注释说明的意思 即 后面的内容lin
  • pip安装更换镜像

    原文链接 使用pip来安装python包有时候安装起来会非常慢 因此需要换成国内的源来加速下载 1 使用命令 以Torch为例 pip install i https pypi tuna tsinghua edu cn simple tor
  • DNS 解析顺序是怎样的?

    1 DNS 解析过程 当客户端对域名发起访问时 会将解析请求发送给递归解析服务器 递归服务器会代替客户端进行全球递归查询 首先递归服务器会请求根域名服务器 根域名服务器根据域名后缀 告知对应的顶级域名服务器 递归服务器再向顶级服务器发起请求
  • Silence - 专注于阅读的博客园主题

    最近花了点心思整理了下我的博客园主题代码 今天正式和大家分享一下 感兴趣的园友可以了解一下 主题介绍 Silence 追求大道至简的终极真理 旨在打造一个干净 专注阅读的博客主题 没有二维空间元素 不存在花里胡哨 简单概括其几个主要特点 专
  • Java面向对象编程

    在 OSI 分层模型中 把传输的比特流划分为帧 是哪一层的功能 A 物理层 B 网络层 C 数据链路层 D 传输层 答案 C 下面关于源端口地址和目标端口地址的描述中 正确的是 A 在TCP UDP传输段中 源端口地址和目的端口地址是不能相
  • Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析 setContentView源码阅读 的讲解 本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 不得不翻阅 ActivityThrea
  • c++中string转UNIX时间戳

    最近的业务 需要用到string转UNIX时间戳 记录一下实现过程 c 代码如下 include
  • Pandas学习笔记

    Pandas学习笔记 1 Pandas介绍 1 1 认识Pandas 1 2 案例 2 Pandas 数据结构 2 1 Series 2 2 DataFram 3 Pandas的基本数据操作 3 1 索引操作 3 2 赋值 3 3 排序 4
  • 各种图论模型及其解答

    原文转自Jelline blog http blog chinaunix net uid 9112803 id 411340 html 本文用另一种思路重新组织 图论及其应用 相关知识 首先 用通俗化语言阐述了如何对事物间联系的问题进行图论
  • C++学习之空间配置器--------(二级空间配置器)

    二级空间配置器 出现的原因 二级空间配置器的原理 二级空间配置器的底层代码解析 出现的原因 原因 由于我们申请的空间有时会太小 而频繁的去申请这样的小空间就会造成太多小额区块造成的内存碎片 对于这些小额区块所带来的不仅仅是内存碎片 更是配置