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

目录

一、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,并合并相邻的spanvoid ReleaseSpanToPageCache(Span* span);Span* NewSpan(size_t k);//获取一个新的span并返回给CentralCachestd::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 _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else//linux 下调用其他
#endifif (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回收小块内存时,查找对应的Spanfor (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.mzph.cn/bicheng/51495.shtml

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

相关文章

Intel 13/14代不稳定 微星率先发声:密切监视、8月中旬更新微码

不久前&#xff0c;Intel针对14/14代酷睿i9 K系列不稳定的问题发布了最新声明&#xff0c;确认问题源于微代码算法缺陷与电压过高&#xff0c;并承诺将在8月中旬完成新版BIOS的验证&#xff0c;随后发放。现在&#xff0c;微星在各家主板厂商中第一个站出来&#xff0c;表明了态…

Java 使用 POI 导出Excel,实现单元格输入内容提示功能

在使用Apache POI的库生成Excel导入模板的时候&#xff0c;有时候需要对单元格能够输入的内容进行一个提示&#xff0c;该如何实现这个特性呢&#xff1f;下面是一个示例代码&#xff0c;演示如何实现单元格输入内容提示功能。 代码 import org.apache.poi.ss.usermodel.*; im…

Frienda 4 件套幽灵狩猎猫球运动发光猫球 LED 运动激活猫球运动点亮猫狗互动玩具宠物发光迷你跑步健身球

来自 美国亚马逊&#xff1a;商品评论: Frienda 4 件套幽灵狩猎猫球运动发光猫球 LED 运动激活猫球运动点亮猫狗互动玩具宠物发光迷你跑步健身球玩具(亮色) (amazon.com) Kim 1.0 颗星&#xff0c;最多 5 颗星 Battery does not last/ cant replace 2024年5月29日 在美国审核…

lora微调Qwen模型全流程

LoRA 微调 Qwen 模型的技术原理概述 LoRA&#xff08;Low-Rank Adaptation&#xff09;是一种用于大模型高效微调的方法。通过对模型参数进行低秩分解和特定层的微调&#xff0c;LoRA 能在保持模型性能的前提下显著减少训练所需的参数量和计算资源。接下来是对 LoRA 微调 Qwen…

鸿蒙开发—黑马云音乐之首页导航栏

目录 1.底部导航 2.点击导航栏的时候点亮 3.新建tabbar对应的页面并加载 1.底部导航 Entry Component struct Index {State message: string 首页BuildertabBuilder(text:string,img:Resource) {// 未选中状态样式处理Column({ space: 5 }) {Image(img).width(25).border…

[C++进阶]抽象类

一、抽象类 1.抽象类的概念 在虚函数的后面写上 0 &#xff0c;则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类&#xff08;也叫接口类&#xff09;&#xff0c;抽象类不能实例化出对象。派生类继承后也不能实例化出对象&#xff0c;只有重写纯虚函数&#xff0c;派生类才…

unity3d:TabView,UGUI多标签页组件,TreeView树状展开菜单

概述 1.最外层DataForm为空壳编辑数据用。可以有多个DataForm&#xff0c;例如福利DataForm&#xff0c;抽奖DataForm 2.Menu层为左边栏层&#xff0c;每个DataForm可以使用不同样式的MenuForm预制体 3.DataForm中使用ReorderList&#xff0c;可排列配置 4.有定位功能&#xf…

Clickhouse 生产集群部署(Centos 环境)

文章目录 机器环境配置安装 JDK 8安装 zookeeperClickhouse 集群安装rpm 包离线安装修改全局配置zookeeper配置Shard和Replica设置image.png添加macros配置启动 clickhouse启动 10.82.46.135 clickhouse server启动 10.82.46.163 clickhouse server启动 10.82.46.218 clickhous…

《InheriBT行为树》For Unity

InheriBT: Unity Editor中的行为树编辑框架 行为树&#xff08;Behavior Tree&#xff09;是一种广泛应用于人工智能&#xff08;AI&#xff09;领域的决策模型&#xff0c;特别是在游戏开发中。行为树通过分层结构和节点的组合&#xff0c;实现了复杂行为的简洁表达。然而&am…

CPU350% JVM GC频繁并GC不掉EXCEL导出

背景&#xff1a; 有个Excel导出的需求&#xff0c;测试的时候&#xff0c;只要连续导出大量的数据就会导致FAT机器反请求反应迟钝&#xff0c;甚至卡死&#xff0c;无法恢复。 排查&#xff1a; 1 跳板机跳到机器上&#xff0c;查看 项目 ipd 执行ps -ef | grep 项目名称.j…

虚拟机Ubuntu20.04 利用串口调试机械臂

虚拟机Ubuntu20.04 利用串口调试机械臂 串口库问题 由于机械臂使用的是串口进行驱动控制&#xff0c;在python中相关的串口库为serial和pyserial两个&#xff0c;这里我曾踩过雷同时安装了serial与pyserial两个库&#xff0c;导致报错如下所示&#xff1a; AttributeError: m…

数据结构:(1)线性表

一、基本概念 概念&#xff1a;零个或多个数据元素的有限序列 元素之间是有顺序了。如果存在多个元素&#xff0c;第一个元素无前驱&#xff0c;最后一个没有后继&#xff0c;其他的元素只有一个前驱和一个后继。 当线性表元素的个数n&#xff08;n>0&am…

使用Spring Boot与Spire.Doc实现Word文档的多样化操作

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 使用Spring Boot与Spire.Doc实现Word文档的多样化操作具有以下优势&#xff1a; 强大的功能组合&#xff1a;Spring Boot提供了快速构建独立和生产级的Spring应用程序的能力&#xff0c;而Spire.Doc则…

OSError: You are trying to access a gated repo.解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

『 Linux 』用户态与内核态的转换机制及信号检测时机

文章目录 用户态与内核态进程地址空间操作系统的本质 信号的处理时机 用户态与内核态 进程在执行代码的过程中代码必定涉及用户代码,库函数代码及操作系统内核代码; 以简单的printf()函数为例,该函数必定为先执行用户的代码即知道需要调用printf()函数,再执行库(如libc)中的代码…

Java线程同步与通信:wait(), notify(), notifyAll(), sleep()

Java线程同步与通信&#xff1a;wait&#xff08;&#xff09;, notify&#xff08;&#xff09;, notifyAll&#xff08;&#xff09;, sleep&#xff08;&#xff09; 1. wait()2. notify()3. notifyAll()4. sleep()4、总结 &#x1f496;The Begin&#x1f496;点点关注&…

一文带你读懂TCP

文章目录 1 TCP协议1.1 TCP 基础1.1.1 TCP 特性1.2.2 TCP连接数 1.2 TCP 头1.2.1 TCP 头格式1.2.2 MTU&#xff0c;MSS&#xff0c;分片传输 1.3 TCP 连接三路握手1.4 TCP 断开四次挥手1.5 SYN攻击和防范1.6 重传机制1.6.1 超时重传1.6.2 快速重传1.6.3 SACK 1.7 滑动窗口1.8 流…

Linux基础复习(二)

前言 本文介绍了一下Linux命令行基本操作及网络配置 一、 命令行提示含义 [当前用户主机名 工作目录]$ 若当前用户是root&#xff0c;则最后一个字符为# 否则&#xff0c;最后一个字符为$ 二、常用Linux命令及其解释 修改主机名 一般在创建一台主机后会使用hostname相关命…

在生信分析中大家需要特别注意的事情​

在生信分析中大家需要特别注意的事情 标准的软件使用和数据分析流程 1. 先看我的b站教学视频 2. 先从我的百度网盘把演示数据集下载下来&#xff0c;先把要运行的模块的演示数据集先运行一遍 3. 前两步都做完了&#xff0c;演示数据集也运行成功了&#xff0c;并且知道了软件…