七、高并发内存池--Page Cache

七、高并发内存池–Page Cache

7.1 PageCache的工作原理

PageCache是以span的大小(以页为单位)和下标一一对应为映射关系的哈希桶,下标是几就说明这个哈希桶下挂的span的大小就是几页的,是绝对映射的关系。因为PageCache也是全局只有唯一一个的,所以为了防止出现线程安全的问题,在访问PageCache之前要先加上一把大锁锁住整个PageCache,为什么这里是加一把大锁锁住整个PageCache而不是加桶锁锁住要访问的那个桶呢?

因为当Central Cache向PageCache获取一个K页的span的时候,在PageCache中下标为K的哈希桶中不一定有span,此时PageCache并不是直接向系统堆中申请一块K页的span内存,因为如果这样处理的话会出现频繁地向系统申请内存的情况,并且每次向堆申请的内存都只是K页大小的,如果每一次的K都是1或者2,这样会使堆申请出来的内存都是很碎片化的,不便于堆做内存管理,所以PageCache的处理策略是如果当前下标的哈希桶中没有span对象,那么就从当前位置开始往后遍历,如果后面的哈希桶中有span对象就把这个span切成一个K页的kspan和一个(n-k)页的span,把kspan切成一个一个的小对象连接到kspan中的自由链表并把这个切好的kspan返回给CentralCache对应位置的哈希桶,剩下的(n-k)页大小的span就挂到PageCache中n-k位置的哈希桶。

如果把PageCache中剩下的所有的哈希桶都遍历完都没有找到一个span,那么就向系统申请一个128(这个128是自定义的)页大小的大span,重复上面的切分操作,最后返回一个切好的K页的span给CentralCache对应位置的哈希桶中,再让CentralCache分若干个小对象给ThreadCache,ThreadCache再返回一个给上层。

综上,也就不难发现PageCache不能用桶锁而是要用一把大锁,因为在CentralCache向PageCache中获取一个span时不仅仅是会访问PageCache中的某一个位置的哈希桶,而是有可能往后遍历其它的哈希桶并访问,所以加桶锁只是锁住了一个桶,并不能锁住后续遍历的其它位置的哈希桶,所以这里要加一把大锁。其实从技术上来说用桶锁也是可以的,只不过在遍历PageCache后面的每一个哈希桶前都要对先对这个桶加上桶锁,访问完之后还要解掉桶锁,如此一来,必然会出现大量的上锁解锁的操作,反而会降低了内存池的效率,所以加一把大锁就行了。

在这里插入图片描述

7.2 PageCache.h

//页缓存,CentralCache需要向PageCache申请K页大小的span,PageCache向系统申请128页大小的span
//因为PageCache也是全局只有唯一一个的,所以也把PageCache设计成单例模式
class PageCache
{
public:static PageCache* GetInstance(){return &_sInst;}//提供给CentralCache向PageCache获取一个k页的span时使用Span* NewSpan(size_t k);//根据obj的地址转换成页号,进而通过哈希表找到页号对应的SpanSpan* MapObjectToSpan(void* obj);//CentralCache把span还回来给PageCachevoid ReleaseSpanToPageCache(Span* span);//PageCache是整个进程唯一的,所以所有的线程都有可能//访问PageCache,存在线程安全问题,所以需要加锁,这里是加一把大锁而不是桶锁std::mutex _pageMtx;private://单例模式需要把构造函数私有化,防止别人创建对象PageCache(){}PageCache(const PageCache&) = delete;private:SpanList _spanLists[NPAGES];//有多少也大小的span数组就有多大,下标和span的大小一一对应//声明静态对象static PageCache _sInst;//如果PageCache中所有的哈希桶都没有span,需要从系统堆中申请内存时//要脱离malloc,所以直接用我们写好的定长内存池申请内存即可,定长内存//池中封装了系统调用接口,直接向堆申请内存和释放内存ObjectPool<Span> _spanPool;//保存页号和span的映射关系,为了后续能找到每一个还回来的小对象属于哪一个spanstd::unordered_map<PAGE_ID, Span*> _idSpanMap;};

7.3 PageCache.cpp


//静态对象需要在类内声明,类外面定义
PageCache PageCache::_sInst;//k代表的是这个span的大小k页
Span* PageCache::NewSpan(size_t k)
{assert(k > 0);//如果申请的span的大小大于128页,则需要直接向堆申请//因为PageCache只有128个有效的哈希桶if (k > NPAGES - 1){//向堆申请k页内存void* ptr = SystemAlloc(k);Span* kSpan = _spanPool.New();//_spanPoll是定长内存池对象,直接向堆申请内存//地址转化成页号kSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;kSpan->_n = k;//把页号和Kspan的映射关系放进Map中_idSpanMap[kSpan->_pageId] = kSpan;return kSpan;}else{//如果PageCache第k个位置的哈希桶上有k页大小的span,则直接返回一个spanif (!_spanLists[k].Empty()){Span* kSpan = _spanLists[k].PopFront();//把kSpan的页号和对应的Span*的映射关系存放到哈希桶中去,方便//CentralCache回收小块内存时,查找对应的span//kSpan代表的是一个k页大小的Span的大块内存,kSpan->_pageId//代表这个大块内存的起始地址,有k页,所以这k页映射到的都是这个Span// //这里曾经没有存映射关系,导致归还小内存块的时候找不到所属的span(细节)for (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageId + i] = kSpan;}return kSpan;}//走到这里说明PageCache第k个位置的哈希桶没有k页大小的span,则需要遍历//后面的大于k页的哈希桶,找到了一个n页大小的span就把这个span切分成一个// k页大小的span和一个n-k页大小的span,k页的返回,n-k页的挂到对应的哈希桶中//遍历后面的哈希桶for (size_t i = k + 1; i < NPAGES; i++){//找到了一个不为空的i页的哈希桶,就对它进行切分if (!_spanLists[i].Empty()){//k页的spanSpan* kSpan = _spanPool.New();//n页的spanSpan* nSpan = _spanLists[i].PopFront();//把这个i页大小的span拿出来进行切分//开始把一个n页的span切分成一个k页的span和一个n-k页的span// //从nSpan的头上切k页给kSpan,所以kSpan的页号就是nSpan的页号kSpan->_pageId = nSpan->_pageId;kSpan->_n = k;//kSpan的页数是k//被切分以后nSpan的页号需要+=k页,因为头nSpan的头k页已经切分给了kSpannSpan->_pageId += k;nSpan->_n -= k;//nSpan的页数要-=k页,因为nSpan被切走了k页//把kSpan的页号和对应的Span*的映射关系存放到哈希桶中去,方便// CentralCache回收小块内存时,查找对应的span//kSpan代表的是一个k页大小的Span的大块内存,kSpan->_pageId//代表这个大块内存的起始地址,有k页,所以这k页映射到的都是这个Spanfor (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageId + i] = kSpan;}//nSpan被切分后的首页和尾页的页号和nspan的映射关系也需要保存起来//以便后续合并,因为合并的方式是前后页合并,往前找肯定找到的是一个span的//最后一页,往后找一定找的是一个span的第一页,所以挂在PageCache对应哈希桶//的span的第一页和最后一页与span的关系也需要保存起来_idSpanMap[nSpan->_pageId] = nSpan;_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;//把剩余的n-k页的span头插到对应下标的哈希桶中,nSpan->_n已经是改过的了,不用再-k_spanLists[nSpan->_n].PushFront(nSpan);return kSpan;}}//走到这里说明前面的NPAGES个哈希桶中都没有Span,(例如第一次申请内存时)//则需要向堆申请一个128页大小的span大块内存,挂到对应的哈希桶中void* ptr = SystemAlloc(NPAGES - 1);Span* bigSpan = _spanPool.New();bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;//内存的地址需要转换成页号映射到对应的哈希桶中bigSpan->_n = NPAGES - 1;//大块内存的页数,描述这个bigspan的大小//把NPAGES-1页大小的span头插到对应NPAGES-1号桶中去_spanLists[bigSpan->_n].PushFront(bigSpan);//本质是运用了复用的设计,避免代码中出现重复的逻辑return NewSpan(k);}}Span* PageCache::MapObjectToSpan(void* obj)
{//计算出obj对应的页号PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT;//访问_idSpanMap的时候需要加锁,避免线程安全的问题//这里使用C++11的RAII锁,出了这个函数这把锁会自动解掉std::unique_lock<std::mutex> lock(_pageMtx);//通过页号查找该内存块对应的是哪一个spanauto ret = _idSpanMap.find(id);if (ret != _idSpanMap.end()){return ret->second;}else{assert(false);return nullptr;}
}//CentralCache把span还回来给PageCache
void PageCache::ReleaseSpanToPageCache(Span* span)
{//如果span的页数大于128页,则说明这个span是从堆上直接申请的,//直接释放给堆即可,不能挂到PageCache的哈希桶中,因为PageCache一个只有128个桶if (span->_n > NPAGES - 1){void* ptr = (void*)(span->_pageId << PAGE_SHIFT);SystemFree(ptr);_spanPool.Delete(span);return;}while (1){//找前一页的span,看是否能够和当前页合并,如果能,则循环向前合并,直到不能合并为止PAGE_ID prevId = span->_pageId - 1;auto ret = _idSpanMap.find(prevId);//_idSpanMap中没找到前一页和对应span,说明前一页的内存没有被申请,结束合并if (ret == _idSpanMap.end()){break;}Span* prevSpan = ret->second;//如果前一页对应的span在CentralCache中正在被使用,结束合并if (prevSpan->_isUse == true){break;}//如果和前一页合并之后会超过哈希桶的最大的映射返回,结束合并if (prevSpan->_n + span->_n > NPAGES - 1){break;}//合并span和prevSpanspan->_pageId = prevSpan->_pageId;span->_n = prevSpan->_n + span->_n;//合并之后需要把prevSpan在对应的哈希桶中删除掉_spanLists[prevSpan->_n].Erase(prevSpan);//因为prevSpan已经被合并到了span中,所以prevSpan对应的内存可以delete掉了_spanPool.Delete(prevSpan);}while (1){//找span的下一个span的起始页号PAGE_ID nextId = span->_pageId + span->_n;auto ret = _idSpanMap.find(nextId);if (ret == _idSpanMap.end()){break;}Span* nextSpan = ret->second;if (nextSpan->_isUse == true){break;}if (nextSpan->_n + span->_n > NPAGES - 1)//曾经写成NPAGES+1了{break;}//span的起始页号不变,页数相加span->_n = span->_n + nextSpan->_n;//合并之后需要把prevSpan在对应的哈希桶中删除掉_spanLists[nextSpan->_n].Erase(nextSpan);_spanPool.Delete(nextSpan);}//合并得到的新的span需要挂到对应页数的哈希桶中_spanLists[span->_n].PushFront(span);//在PageCache中的span要设置为false,好让后面相邻的span来合并span->_isUse = false;//为了方便后续的合并,需要把span的起始页号和尾页号和span建立映射关系_idSpanMap[span->_pageId] = span;_idSpanMap[span->_pageId + span->_n - 1] = span;}

以上就是高并发内存池三层模型的核心部分,后面的文章是对该高并发内存池的性能测试以及针对性能的瓶颈区做出相应的优化策略。

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

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

相关文章

Vue2项目练手——通用后台管理项目第三节

Vue2项目练手——通用后台管理项目 首页组件布局个人信息展示使用的组件App.vueHome.vue 列表信息展示使用的组件Home.vue 订单统计Home.vue 数据的请求axios的基本使用二次封装文件目录src/api/index.jssrc/utils/request.jsHome.vue 首页组件布局 个人信息展示 使用的组件 …

多应用模式下,忽略项目的入口文件,重写Apache规则

多应用模式下&#xff0c;忽略项目的入口文件&#xff0c;重写Apache规则 首先&#xff0c;我的项目是具有两个应用&#xff0c;admin和index,同时给它们绑定了域名&#xff0c;但是每次访问时都需要加入项目的入口文件地址 index.php ,为了忽略这个入口文件&#xff0c;只能通…

Windows安装FFmpeg说明

下载地址 官网 Download FFmpeg Csdn ffmpeg安装包&#xff0c;ffmpeg-2023-08-28-git-b5273c619d-full-build.7z资源-CSDN文库 解压安装&#xff0c;添加环境变量 命令行输入ffmpeg 安装成功

2023开学礼《乡村振兴战略下传统村落文化旅游设计》北农馆藏许少辉八一新书

2023开学礼《乡村振兴战略下传统村落文化旅游设计》北京农学院图书馆许少辉八一新书

react快速开始(三)-create-react-app脚手架项目启动;使用VScode调试react

文章目录 react快速开始(三)-create-react-app脚手架项目启动&#xff1b;使用VScode调试react一、create-react-app脚手架项目启动1. react-scripts2. 关于better-npm-runbetter-npm-run安装 二、使用VScode调试react1. 浏览器插件React Developer Tools2. 【重点】用 VSCode …

44、基于 AOP 的错误处理,相当于异常拦截处理

基于 springboot 自动配置的 spring mvc 错误处理&#xff0c;就是演示项目报错后&#xff0c;跳转到自定义的错误页面 ★ 两种错误处理方式 方式一&#xff1a; 基于Spring Boot自动配置的错误处理方式&#xff0c;只要通过属性文件即可配置错误处理行为。 提供自定义的错误…

手写Mybatis:第7章-SQL执行器的定义和实现

文章目录 一、目标&#xff1a;SQL执行的定义和实现二、设计&#xff1a;SQL执行的定义和实现三、实现&#xff1a;SQL执行的定义和实现3.1 工程结构3.2 SQL执行实现的关系图3.3 执行器的定义和实现3.3.1 Executor 接口3.3.2 BaseExecutor 抽象基类3.3.3 SimpleExecutor 简单执…

GPU版本pytorch(Cuda12.1)安装教程

我们通过Pytorch官网安装torch的时候&#xff0c;会发现常常由于网速问题安装不成功&#xff0c;下面提供一种简单的方法可以成功安装Cuda12.1&#xff0c;亲测有效。 目录 一、常规方法 二、有效方法 2.1 创建并激活虚拟环境 2.2 添加清华源 2.3 安装torch 一、常规方法…

xsschallenge通关(11-15)

level 11 老规矩&#xff0c;先查看源码&#xff0c;做代码审计&#xff1a; <?php ini_set("display_errors", 0); $str $_GET["keyword"]; $str00 $_GET["t_sort"]; $str11$_SERVER[HTTP_REFERER]; $str22str_replace(">&quo…

Linux centos7 bash编程(小练习)

一、打印九九乘法口诀 这一个for循环嵌套的小练习&#xff0c;难度不大。提供一种写法&#xff0c;供参考&#xff1a; #!/bin/bash # 文件名&#xff1a;99table.sh # 打印输出九九乘法口诀表 for i in {1..9} do for ((j1;j<$i;j)) do …

雅思写作 三小时浓缩学习顾家北 笔记总结(三)

目录 顾家北饥饿网100个句子翻译 "Heritage sites threatened by urban development" "Heritage sites are threatened by urban development." We should not ignore face-to-face communication. We cannot ignore face-to-face communication. So…

使用Python对数据的操作转换

1、列表加值转字典 在Python中&#xff0c;将列表的值转换为字典的键可以使用以下代码&#xff1a; myList ["name", "age", "location"] myDict {k: None for k in myList} print(myDict) 输出&#xff1a; {name: None, age: None, loca…

给oracle逻辑导出clob大字段、大数据量表提提速

文章目录 前言一、大表数据附&#xff1a;查询大表 二、解题思路1.导出排除大表的数据2.rowid切片导出大表数据Linux代码如下&#xff08;示例&#xff09;&#xff1a;Windows代码如下&#xff08;示例&#xff09;&#xff1a;手工执行代码如下&#xff08;示例&#xff09;&…

C++11——右值引用和移动语义

✅<1>主页&#xff1a;&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C11——右值引用 ☂️<3>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<4>前言&#xff1a;右值引用&#xff0c;是C11更新的一个非常有价值的语法&am…

【网络安全防护】上海道宁与Bitdefender帮助您构建弹性网络并降低安全运营成本

在网络的世界中 风险变得更加常见与复杂 企业需要从网络安全转向网络弹性 复杂的网络攻击已非常普遍 在面临攻击时 企业如何保持业务连续性&#xff1f; Bitdefender GravityZone将 风险分析、安全加固、威胁预防 检测和响应功能相结合 帮助您构建弹性网络 并降低安全…

LinkedHashMap实现LRU缓存cache机制,Kotlin

LinkedHashMap实现LRU缓存cache机制&#xff0c;Kotlin LinkedHashMap的accessOrdertrue后&#xff0c;访问LinkedHashMap里面存储的元素&#xff0c;LinkedHashMap就会把该元素移动到最尾部。利用这一点&#xff0c;可以设置一个缓存的上限值&#xff0c;当存入的缓存数理超过…

手摸手2-springboot编写基础的增删改查

目录 手摸手2-springboot编写基础的增删改查创建controller层添加service层接口service层实现添加mapper层mapper层对应的sql添加扫描注解,对应sql文件的目录 手摸手2-springboot编写基础的增删改查 创建controller层 实现 test 表中的添加、修改、删除及列表查询接口&#x…

Unity——工程与资源

本文将详细介绍Unity工程的文件夹结构&#xff0c;以及动态加载资源的技术要点 一、Unity项目的文件夹结构 1.工程文件夹 在新建工程时&#xff0c;Unity会创建所有必要的文件夹。第一级文件夹有Assets,Library,Logs,Packages,ProjectSettings。 Assets&#xff1a;最主要的文…

stable diffusion实践操作-提示词插件安装与使用

本文专门开一节写提示词相关的内容&#xff0c;在看之前&#xff0c;可以同步关注&#xff1a; stable diffusion实践操作 正文 1、提示词插件安装 1.1、 安装 1.2 加载【应用更改并重载前端】 1.3 界面展示 1.3.-4 使用 里面有个收藏列表&#xff0c;可以收藏以前的所有提示…

CSC2121A

半桥架构的栅极驱动电路CSC2121A CSC2121系列是一款高性价比的半桥架构的栅极驱动专用电路&#xff0c;用于大功率MOS管、IGBT管栅极驱动。IC内部集成了逻辑信号处理电路、死区时间控制电路、欠压保护电路、电平位移电路、脉冲滤波电路及输出驱动电路&#xff0c;专用于无刷电…