高并发内存池(四)Page Cache的框架及内存申请实现

news2024/11/17 16:33:06

目录

一、Page Cache的框架梳理

二、Page Cache的实现

2.1PageCache.h

2.2VirtualAlloc

2.3std::unordered_map _idSpanMap,>

2.4Page Cache.cpp


一、Page Cache的框架梳理

申请内存:

1. 当central cache向page cache申请内存时,page cache先检查对应位置有没有span,如果没有 则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4页page,4页page后面没 有挂span,则向后面寻找更大的span,假设在10页page位置找到一个span,则将10页page span分裂为一个4页page span和一个6页page span。

2. 如果找到_spanList[128]都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式 申请128页page span挂在自由链表中,再重复1中的过程。

3. 需要注意的是central cache和page cache 的核心结构都是spanlist的哈希桶,但是他们是有本质 区别的,central cache中哈希桶,是按跟thread cache一样的大小对齐关系映射的,他的spanlist 中挂的span中的内存都被按映射关系切好链接成小块内存的自由链表。而page cache 中的 spanlist则是按下标桶号映射的,也就是说第i号桶中挂的span都是i页内存。

二、Page Cache的实现

2.1PageCache.h

PageCache中也同样采用了单例模式,和互斥锁。但PageCache不同于centralcache,只有一个全局锁,thread和central哈希桶中挂的链表结构都是彼此单独管理不会相互影响,而PageCache中的桶是根据页数来直接映射的,从1-128大小的页(一页是8kb),刚开始page向进程地址空间中的堆进行申请时,直接申请的就是一大页,所以内存池刚跑起来时,整个SpanList中只有最大的那个桶中有一个128*8kb的大页,而小页都是由大页切出来的,将大页切小以后放到对应页所在的桶里,所以整个pagecache桶都是动态进行变化的。
    当线程顺着Central找到Pagecache后会根据用户申请的内存大小计算出要去Page Cache的哪个桶去切那个桶的Span中的页,如果目标桶没有就从当前桶往下去一层一层查找大页桶里是否有内存,如果有就切出自己想要的小页内存,然后将剩下的放到另一个桶中。如果此时有桶锁,多个线程同时去查找切分再放回去,就会导致频繁的加锁解锁,反而降低了效率。所以在PageCache中直接挂一把整个单例的大锁,每次只允许一个线程访问

class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_sInst;
	}

	//获取当前地址到span的映射
	Span* MapObjectToSpan(void* obj);

	// 释放空闲span回到Pagecache,并合并相邻的span
	void ReleaseSpanToPageCache(Span* span);


	Span* NewSpan(size_t k);//获取一个新的span并返回给CentralCache

	std::mutex _pageMtx;

private:
	SpanList _spanList[NPAGES];//按一个span的页数来存放span,从1页到128页,下标从1-128,带上0号下标不存数据一共129个桶

	std::unordered_map<PAGE_ID, Span*> _idSpanMap;

	PageCache()
	{}
	PageCache(const PageCache&) = delete;

	static PageCache _sInst;//创建单例模式,因为PageCache也是全局共享的
};

2.2VirtualAlloc

VirtualAlloc是一个Windows API函数,该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页。当Page Cache内存不够时,就可以调用该函数来直接向堆申请内存。

简单点的意思就是申请内存空间。VirtualAlloc这个函数保证申请的大页内存都是页框大小的整数倍。也就是说起始地址就是8kb的整数倍开始的。所以我们在代码中直接将分配来的地址/8kb来计算页号时,也就不用担心移位操作导致地址低位丢失的问题。

//直接去堆上申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	//linux 下调用其他
#endif
	if (ptr == nullptr)
		throw std::bad_alloc();

	return ptr;
}

2.3std::unordered_map<PAGE_ID, Span*> _idSpanMap

      与Central Cache和Thread Cache有所不同的是,Page Cache中的成员变量管理内存块的SpanList _spanList[NPAGES],还有另一个哈希结构,unordered_map<PAGE_ID, Span*> _idSpanMap;

      其作用是为了将页号固定映射到对应的span,和freelist的内存对齐一样,页号也存在对齐,VirtualAlloc申请的内存全是页框大小的整数倍。而因为central cache在向thread cache分配切好的小块span时,这个线程需要就从头部区内存块给这个线程,而另一个线程来了就要分给另一个线程。

      所以多线程环境下,线程中每个freelist中的小块内存可能并不是连续的,而在回收内存时无论是在central cache层还是page cache层,其内存块中的地址都是连续的,所以Thread Cache释放的小块内存都需要将其回归到Central Cache中原本它所在的Span当中,当Span中的数据都回来时,就可以合成一个(多个)完整的页,而要想让小块内存回到原本的Span,就需要找到这个给小块内存的页号是和哪个Span所对应的,而为了方便寻找,直接在切分时就对每一页的页号做映射,将页号直接与span地址进行映射方便回收时查找span,直接通过推算得出小块内存页号后直接找到对应Span地址。

      所以在Page Cache中有一个哈希表,专门记录分配下去的Span中所存储的起始页号和该Span的映射关系。为之后我们的回收逻辑的实现做铺垫。

2.4Page Cache.cpp

首先要实现的是NewSpan,用于处理Central Cache发来的内存申请请求,并返回一个Span,根据Central Cache所要申请的页的个数,去对应的桶中拿取,如果桶里有就直接返回给Central Cache,如果没有就去存放更大的页的桶里切对应的小页,并且建立切走的页的起始页号以及根据切走了多少页,将每一页都和要返回给Central Cache的Span建立映射关系。然后将切出来的Span返回给Central Cache,将切剩下的页根据存放其Span中剩余页的总数头插到对应的桶中。

#include "PageCache.h"

PageCache PageCache::_sInst;

Span* PageCache::NewSpan(size_t k)//需要找到span为k页的那个桶
{
	assert(k > 0 && k < NPAGES);
	if (!_spanList[k].Empty())
	{
		return _spanList[k].PopFront();//如果该桶下不为空,就将第一个span返回给central cache
	}
	for (size_t i = k + 1; i < NPAGES; i++)
	{
		//假设找到的桶内有n页,将其切分成一个k页的span和一个n-k页的span
		//k页的span返回给central cache
		//n-k页的span挂到第n-k个桶
		if (!_spanList[i].Empty())
		{
			Span* nSpan = _spanList[i].PopFront();//程序第一次刚运行走到这里时,此时i对应的一定是127,也就是第128个桶
			Span* kSpan = new Span; 

			//在nSpan头部切一个k页下来
			kSpan->_pageId = nSpan->_pageId;
			kSpan->_n = k;//将新创建的Span中的页设置成我们需要的页的大小
			
			nSpan->_pageId += k;//取走k页是从当前Span头部切出k页,然后原本nSpan所指向的页号就要往后走k页,这样在之后对页号进行转换时就可以直接转换出切走k页后的页号
			nSpan->_n -= k;//nSpan中页的数量减少k
			//注意:这里只是抽象的说从nspan头部且一块下来,实际上切的不是nspan,
			//而是nsapn中的页号(由地址/8k所得到的页号),切实际上就是将页号往后移,从而改变Span中保存的地址

			//而这里存储nspan中的首位页号映射到nspan也是为了更好的方便page cache回收内存时进行的合并查找
			_idSpanMap[nSpan->_pageId] = nSpan;
			_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;

			//将每页id和当前所持有该页的span建立映射,方便central cache回收小块内存时,查找对应的Span
			for (size_t i = 0; i < kSpan->_n; ++i)
			{
				_idSpanMap[kSpan->_pageId + i] = kSpan;
			}
			_spanList[nSpan->_n].PushFront(nSpan);

			return kSpan;
		}
	}

	//走到这个位置就说明后面没有大页的span
	//这时就去找堆要一个128页的span,然后再根据centralcache的需求将其切成不同的小页span,然后分别放入对应的桶中
	Span* bigSpan = new Span; 
	void* ptr = SystemAlloc(NPAGES - 1); 

	bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;//此时当前Span中的页数是128页

	_spanList[bigSpan->_n].PushFront(bigSpan);//根据当前span所持有的页数,将当前span插入到对应的桶中

	return NewSpan(k);//这下128页所对应的桶中已经有内存了,再走一遍递归逻辑去128页对应的桶中切k页
}

//说明在PageCache.h
//直接拿着地址换算出页号(直接就是对齐)
//virtualalloc申请的内存起始就是8k的整数被
//比如起始内存除8k=1
//那么从起始地址往后的8k-1个地址除8K得出的也都是1.xxx,除完还是1
Span* PageCache::MapObjectToSpan(void* obj)
{
	PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
	auto ret = _idSpanMap.find(id);
	if (ret != _idSpanMap.end())
	{
		return ret->second;
	}
	else
	{
		assert(false);
		return nullptr;
	}
}

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

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

相关文章

2024年7月29日 十二生肖 今日运势

小运播报&#xff1a;2024年7月29日&#xff0c;星期一&#xff0c;农历六月廿四 &#xff08;甲辰年辛未月甲午日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;羊、虎、狗 需要注意&#xff1a;兔、牛、鼠 喜神方位&#xff1a;东北方 财神方位&#xff1a;…

论文阅读:Deformable DETR: Deformable Transformers for End-to-End Object Detection

论文阅读&#xff1a;Deformable DETR: Deformable Transformers for End-to-End Object Detection Deformable DETR: 基于稀疏空间采样的注意力机制&#xff0c;让DCN与Transformer一起玩&#xff01; - 知乎 (zhihu.com) 【Deformable DETR 论文源码解读】Deformable Trans…

Linux嵌入书学习—数据结构——栈(seqstak)

一、栈&#xff1b; 定义&#xff1a; 是限定仅在表尾&#xff08;栈顶&#xff09;进行插入和删除操作的线性表 栈又称为 后进先出&#xff08;Last In First Out&#xff09; 的线性表&#xff0c;简称 LIFO 结构 栈顶&#xff08;Top&#xff09; 栈顶是栈中允许进行添加&…

构建大规模账号池与本地部署:GitHub爬虫项目详解

账号池搭建 必要性 常见登录方式&#xff1a; 基于Session Cookie的登录基于JWT的登录&#xff1a;登录生成JWT字符串 账号池存储cookie或者JWT字符串 方便后续发请求爬取数据 本地部署 conda建立一个虚拟环境 conda create -n new_env python3.x # 替换 x 为你需要的 P…

【 C++ 】 类和对象的学习

前言&#xff1a; &#x1f618;我的主页&#xff1a;OMGmyhair-CSDN博客 目录 引言&#xff1a; 一、类的作用域 二、计算类对象的大小 三、this指针 this指针❓1 this指针❓2 this指针❓3 引言&#xff1a; 通过类我们可以对数据和方法进行封装 封装的意义&#xf…

【Android】实现一个优雅的自定义底部导航栏(速通安卓大作业必备)

文章目录 前言一、实现思路二、代码实现流程①修改theme&#xff1a;②在color文件中添加颜色&#xff1a;③添加图标文件④添加选中时布局的背景⑤修改布局文件⑥按钮效果图&#xff1a;⑦修改MainActivity中的代码⑦创建各个界面的Fragment⑧运行结果&#xff1a; 三、 总结 …

【学术会议征稿】第五届人工智能与教育国际学术会议(ICAIE 2024)

第五届人工智能与教育国际学术会议&#xff08;ICAIE 2024&#xff09; 2024 5th International Conference on Artificial Intelligence and Education 第五届人工智能与教育国际学术会议&#xff08;ICAIE 2024&#xff09;由集美大学诚毅学院主办&#xff0c;闽南师范大学…

福昕PDF编辑器v13专业版 授权版

福昕高级PDF编辑器是一款功能强大的PDF文件编辑软件&#xff0c;提供多种实用的编辑功能。 软件截图&#xff1a; 使用说明&#xff1a; 解压后&#xff0c;双击start.bat来运行软件 下载地址&#xff1a;FoxitPDFEditor-Pro-v13 解压密码&#xff1a;helloh 下载时可能会有…

动手学大模型应用开发笔记--用dash开发一个大模型知识库

简介 动手学&#xff0c;把自己学到的东西动手自己做出来并输出&#xff0c;是最好的学习方式。最近一直在关注和使用各种ai工具&#xff0c;也在学一些ai开发的知识&#xff0c;看到datawhale的开源学习教程&#xff0c;动手学大模型开发( [github.com/datawhalech…])这个教…

高频面试题基本总结回顾(含笔试高频算法整理)暂存篇

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

dockerfile部署镜像 ->push仓库 ->虚拟机安装建木 ->自动部署化 (详细步骤)

目录 创建私服仓库 vi /etc/docker/daemon.json vim deploy.sh判断脚本内容 创建 建木 后端部署 命名空间 设置密码用户名 创建git仓库 gitignore文件内容 图形项目操作 git maven docker镜像 点击流程日志 vim /etc/docker/daemon.json 执行部署脚本 ip 开发…

代码性能优化(3)——聊聊多线程

代码的性能优化&#xff0c;有些是从逻辑层面进行的&#xff0c;比如同时对50W个人发放奖励&#xff0c;可以改成用户登录的时候&#xff0c;自动领取有没奖励&#xff0c;或者统计每日的每个业务员的销售额和实时累积的销售额&#xff0c;将实时sum函数改成&#xff0c;每一笔…

24种设计模式介绍与6大设计原则(电子版教程)

前言 您是一个初级的 coder,可以从中领会到怎么设计一段优秀的代码&#xff1b;您是一个高级程序员&#xff0c;可以从中全面了解到设计模式以及 Java 的边角技术的使用&#xff1b;您是一个顶级的系统分析师&#xff0c;可以从中获得共鸣&#xff0c;寻找到项目公共问题的解决…

StarRock3.3 安装部署

服务器前置要求&#xff1a; 1、内存>32GB 2、JDK 8 is not supported, please use JDK 11 or 17 1、安装 wget https://releases.starrocks.io/starrocks/StarRocks-3.3.0.tar.gz tar zxvf StarRocks-3.3.0.tar.gz 2、FE服务启动 2.1 配置FE节点(默认配置&#xff0c;…

dns和 openELB

DNS yum -y install bind允许其他的主机来监听&#xff0c;允许其他的主机来查询&#xff0c;改这两个地方就行了。 把需要解析的文件都添加进来&#xff0c;cp -p的意思是保留原来的权限控制 注意本地dns放在 DNS1 二、负载均衡 OpenELB Layer2 模式 BGP模式 OpenELB …

DBeaver使用SQL脚本编辑器

文章目录 1 新建脚本2 选择数据库3 编写脚本【按行执行】参考 1 新建脚本 2 选择数据库 3 编写脚本【按行执行】 光标放到需要执行的行上&#xff0c;点击【最上面的按钮】 或者选中某片代码&#xff0c;然后执行 也可以编写一个脚本然后执行 参考 dbeaver安装和使用教程 …

Linux文件恢复

很麻烦 一般还是小心最好 特别恢复的时候 可能不能选择某个文件夹去扫描恢复 所以 删除的时候 用rm -i代替rm 一定小心 以及 探索下linux的垃圾箱机制 注意 一定要恢复到不同文件夹 省的出问题 法1 系统自带工具 debugfs 但是好像不能重启&#xff1f; testdisk 1、安装 …

酒店智能门锁接口pro[0922]D801 对接收银-SAAS本地化-未来之窗行业应用跨平台架构

proUSB接口函数[0922中性版]-D801 调用函数库&#xff1a; 提供Windows下的32位动态连接库proRFL.DLL&#xff0c;函数使用详细说明 //-----------------------------------------------------------------------------------// 功能&#xff1a;读DLL版本&#xff0c;不涉…

【大数据】:hdfs相关进程启停管理命令

HADOOP_HOME/sbin/start-dfs.sh&#xff0c;一键启动HDFS集群 执行原理&#xff1a; 在执行此脚本的机器上&#xff0c;启动SecondaryNameNode 读取core-site.xml内容&#xff08;fs.defaultFS项&#xff09;&#xff0c;确认NameNode所在机器&#xff0c;启动NameNode 读取wor…

AI变现:科技与商业化的交织

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经从科幻电影中的概念走进了现实生活的各个领域&#xff0c;深刻影响着经济、社会与科技的发展。AI不仅代表着技术的革新&#xff0c;更是推动商业变现的重要力量。本文将深入剖析AI的发展历程&#xff0c;…