【项目日记(七)】第三层: 页缓存的具体实现(上)
💓博主CSDN主页:杭电码农-NEO💓
⏩专栏分类:项目日记-高并发内存池⏪
🚚代码仓库:NEO的学习日记🚚
🌹关注我🫵带你做项目
🔝🔝
开发环境:Visual Studio 2022

项目日记
- 1. 前言
- 2. 页缓存的具体结构
- 3. 页缓存分配内存的全过程
- 4. 页缓存分配内存的代码实现
- 5. 优化代码,并完全脱离malloc
- 6. 总结以及代码拓展
1. 前言
在页缓存这一层中,负责给中心缓存
分配大块儿的内存,以及合并前后空
闲的内存,这一层为整体加锁!
本章重点:
本篇文章着重讲解内存池第三层:
页缓存的基本成员变量和函数,以
页缓存的具体结构是怎样的.了解
完基础结构后,会详解讲解中心缓存
层来申请内存时的具体步骤!
2. 页缓存的具体结构
页缓存也是一个哈希桶结构,但它的映射
规则和前两层不同,它的规则是:
K号桶中的大块儿内存就是K页
并且它总共是有128号桶,申请小于
128页的内存都会在内存池中申请

//单例模式
class PageCache
{
public:static PageCache* GetInstance(){return &_singleton;}//获取一个K页的spanSpanData* NewSpan(size_t k);std::mutex _mtx;//pagecache不能用桶锁,只能用全局锁,因为后面可能会有大页被切割为小页// 获取从对象到span的映射,给我一个地址,返回这个地址对应的spanSpanData* MapObjectToSpan(void* obj);// 释放空闲span回到Pagecache,并合并相邻的spanvoid ReleaseSpanToPageCache(SpanData* span);
private:PageCache(){}PageCache(const PageCache& obj) = delete;
private:std::unordered_map<PAGE_ID, SpanData*> _idSpanMap;//存储页号和桶中对应的span的映射,解决换回来的内存对应哪个span的问题SpanList _spanList[N_PAGES];static PageCache _singleton;
};
3. 页缓存分配内存的全过程
当中心缓存中没有内存时,会去页缓存
申请一个span结构,要经过下面几步:
- 根据中心缓存的桶号来确定申请的span是几页的
- 根据中心缓存想要申请的页数,找到页缓存中对应的桶(k页对应k号桶)
- 情况一: 页缓存的K号桶中存在span结构,直接将这块儿内存返回给中心缓存
- 情况二: 页缓存的K号桶没有span结构,但是K+1到128号桶中存在span结构,假设n号桶有span,则将这个大页的span切分为一个k页的span和一个n-k页的span,k页的span返回给中心缓存去使用,而将n-k页的span重新挂在n-k号桶中
- 情况三: k到128号桶都没有span,此时页缓存会向系统申请一份128页大小的内存,并挂在128号桶中,再将这个128页的span切分为k页的span和128-k页的span,也就转换为了情况二!

并且在这个过程中,页缓存将一个span
分配给中心缓存后,会记录下来这块儿
内存的页号和span的映射关系,方便后续
回收内存的时候再使用!
4. 页缓存分配内存的代码实现
在pagecache.h文件中:
SpanData* PageCache::NewSpan(size_t k)//去第K个桶中找span给central,此i号桶中挂的span都是i页内存
{//若K桶中有,直接返回,K桶没有span就往后找去分裂大spanassert(k > 0);if (k > N_PAGES - 1)//如果申请的页数大于了128页,pagecache只能向堆申请了{void* ptr = SystemAlloc(k);SpanData* span = new SpanData();span->_pageid = (PAGE_ID)ptr >> PAGE_SHIFT;span->_n = k;_idSpanMap[span->_pageid] = span;return span;}//先检查K号桶有无span,有直接返回一个if (!_spanList[k].Empty()){SpanData* KSpan = _spanList[k].PopFront();for (PAGE_ID i = 0; i < KSpan->_n; i++)_idSpanMap[KSpan->_pageid + i] = KSpan;return KSpan;}//走到这儿代表k号桶为空,检查后面的桶有没有span,拿出来分裂成两个小spanfor (int i = k + 1; i < N_PAGES; i++){if (!_spanList[i].Empty())//k页的span返回给centralcache,i-k页的span挂到i-k号桶中{SpanData* ISpan = _spanList[i].PopFront();SpanData* KSpan = new SpanData;KSpan->_pageid = ISpan->_pageid;KSpan->_n = k;ISpan->_pageid += k;//把头K页切分给KSpanISpan->_n -= k; //页数从i变为i-k_spanList[ISpan->_n].PushFront(ISpan);//再将后i-k页分配给i-k号桶//存储Ispan的首尾页号跟ISpan的映射关系// 这里只需要映射首尾页而不需要像下面一样全部页都映射,因为下面切分出去的span会被切分为小块儿内存// 这些小块儿内存都有可能被使用,所以当它们还回来时这些小块儿内存可能映射的是不同的页,但这些页都属于这个KSpan// 然而ISpan中不会被切分为小块儿内存,它只需要关心是否和它的前后页合并,所以这里只需要映射首尾页号与ISpan的关系// ISpan作为要合并页的前面,如1000页要合并ISpan是1001页,那么1001到1001+n都是空闲的!ISpan作为要合并页的后面,如100页要合并ISpan是999页,那么999-n都是空闲的!//_idSpanMap[ISpan->_pageid] = ISpan;//_idSpanMap[ISpan->_pageid + ISpan->_n - 1] = ISpan;_idSpanMap.set(ISpan->_pageid, ISpan);_idSpanMap.set(ISpan->_pageid + ISpan->_n - 1, ISpan);//建立id和span的映射关系,方便centralcache回收小块内存时查看哪块内存在哪块spanfor (PAGE_ID i = 0; i < KSpan->_n; i++)//返回的KSpan中一共有n页,并且每一页的页号都对应KSpan这个地址_idSpanMap[KSpan->_pageid + i] = KSpan;return KSpan;}}//走到这里说明后面所有的桶都没有span了//这时需要向堆申请一个128页的span再拿来做切分SpanData* bigSpan = _spanPool.New();void* ptr = SystemAlloc(N_PAGES - 1);bigSpan->_pageid = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = N_PAGES - 1;_spanList[bigSpan->_n].PushFront(bigSpan);//将这个128页的span插入到桶中return NewSpan(k);//再次调用自己,这次一定会在前面的for循环处返回
}
这个地方有一个设计的比较巧妙的点,
那就是出现情况三的时候,向系统申请了
128页的空间后,再次调用这个函数就一定
会出现情况二,从而在for循环中走完整个过程
5. 优化代码,并完全脱离malloc
细心的同学会发现,在这个函数中使用到了new操作符,然而了解new底层原理的同学应该知道,new的底层实际上是用的malloc来申请的空间,但是我们这个项目就是为了完全脱离malloc函数来实现一个多线程下高效的内存池,所以这里一定不能使用new!
使用之前编写的定长池来舍弃new!
如果你不知道或忘记了定长池是什么
请看这篇文章: 定长池的实现
首先, 在页缓存类中添加上一个成员变量: 定长池类, 然后在使用new的地方,把new全部替换为用定长池申请空间!


6. 总结以及代码拓展
页缓存分配内存的一环设计的是
非常的巧妙,但是页缓存真正巧妙
的地方是在合并空闲内存的一环!
对代码的拓展:
我们会发现页缓存结构中调用了
好几次向系统申请内存的函数,
这个地方只做了解,会用接口就行
inline static void* SystemAlloc(size_t kpage)//申请kpage页内存
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等直接向系统申请内存的方式
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}
相关文章:
【项目日记(七)】第三层: 页缓存的具体实现(上)
💓博主CSDN主页:杭电码农-NEO💓 ⏩专栏分类:项目日记-高并发内存池⏪ 🚚代码仓库:NEO的学习日记🚚 🌹关注我🫵带你做项目 🔝🔝 开发环境: Visual Studio 2022 项目日…...
深入解剖指针篇(2)
目录 指针的使用 strlen的模拟实现 传值调用和传址调用 数组名的理解 使用指针访问数组 一维数组传参的本质 冒泡排序 个人主页(找往期文章):我要学编程(ಥ_ಥ)-CSDN博客 指针的使用 strlen的模拟实现 库函数strlen的功能是求字符串…...
【知识点】Java常用
文章目录 基础基础数据类型内部类Java IOIO多路复用重要概念 Channel **通道**重要概念 Buffer **数据缓存区**重要概念 Selector **选择器** 关键字final 元注解常用接口异常处理ErrorException JVM与虚拟机JVM内存模型本地方法栈虚拟机栈 Stack堆 Heap方法区 Method Area (JD…...
MySQL篇—迁移数据目录到新的本地路径
☘️博主介绍☘️: ✨又是一天没白过,我是奈斯,DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux,也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章,并且也会默默的点赞收藏加关注❣…...
【FPGA】高云FPGA之IP核的使用->PLL锁相环
FPGA开发流程 1、设计定义2、设计输入3、分析和综合4、功能仿真5、布局布线6、时序仿真7、IO分配以及配置文件(bit流文件)的生成8、配置(烧录)FPGA9、在线调试 1、设计定义 使用高云内置IP核实现多路不同时钟输出 输入时钟50M由晶…...
程控设备和电脑通信的总线和协议选择
文章目录 程控设备都通过什么协议和总线和电脑通信?工控设备都使用什么通信协议与电脑通信?各种工控设备通信协议的优缺点如何选择适合工控设备的通信协议?各种工控设备通信总线的优缺点如何判断一种总线是否适合特定的应用场景?程控设备都通过什么协议和总线和电脑通信? …...
鸿蒙开发-UI-组件导航-Navigation
鸿蒙开发-UI-组件 鸿蒙开发-UI-组件2 鸿蒙开发-UI-组件3 鸿蒙开发-UI-气泡/菜单 鸿蒙开发-UI-页面路由 文章目录 目录 一、基本概念 二、页面显示模式 1.自适应模式 2.单页面模式 3.分栏模式 三、标题栏模式 1.Mini模式 2.Full模式 四、菜单栏 五、工具栏 六、案例 …...
Codeforces Round 922 (Div. 2)(A~D)补题
A题考虑贪心,要使使用的砖头越多,每块转的k应尽可能小,最小取2,最后可能多出来,多出来的就是最后一块k3,我们一行内用到的砖头就是 m 2 \frac{m}{2} 2m下取整,然后乘以行数就是答案。 #inclu…...
Seata下载与配置、启动
目录 Seata下载Seata配置启动Seata Seata下载 首先,我们需要知道我们要使用哪个版本的seata,这就要查看spring-cloud-alibaba版本说明,找到我们对应的seata。 spring-cloud-alibaba版本说明: 地址链接 下面是部分版本说明: s…...
什么是TCP三次握手、四次挥手?
1、三次握手 你(客户端)给一个朋友(服务器)打电话,告诉他你想开始对话。这就像是发送一个SYN(同步序列编号)信号,表示你想开始建立连接。(client向server发送syn,seqx,此时client验证client发送能力正常。client置为SYN SENT状态)…...
C++程序在开机自启和定时器执行时遇到的问题和解决方法
遇到的错误如下: Camera is created.load vfvlog.[dll/so] failed for dll[/vfvlog.so] unexistedLoadDbgConfig, LoadFile fail, err:-3, errno: No such file or directoryqt.qpa.xcb: could not connect to displayqt.qpa.plugin: Could not load the Qt platfo…...
R17 extended DRX(eDRX)
根据工业无线传感器的要求,电池至少可持续使用数年。 在NB-IoT和LTE eMTC中,也有类似的要求。 为了满足极长电池寿命的要求,NB-IoT和LTE eMTC在Release 13中为RRC IDLE UE引入了扩展DRX,在Release 16中为RRC INACTIVE UE引入了eDRX,上面是LTE 引入eDRX的背景。 一 概述 到…...
Debezium发布历史102
原文地址: https://debezium.io/blog/2021/02/24/debezium-1-5-beta1-released/ 欢迎关注留言,我是收集整理小能手,工具翻译,仅供参考,笔芯笔芯. Debezium 1.5.0.Beta1 发布 2021 年 2 月 24 日 作者: Gu…...
探索自然语言处理在改善搜索引擎、语音助手和机器翻译中的应用
文章目录 每日一句正能量前言文本分析语音识别机器翻译语义分析自然语言生成情感分析后记 每日一句正能量 努力学习,勤奋工作,让青春更加光彩。 前言 自然语言处理(NLP)是人工智能领域中与人类语言相关的重要研究方向,…...
echarts:获取省、市、区/县、镇的地图数据
目录 第一章 前言 第二章 获取地图的数据(GeoJSON格式) 2.1 获取省、市、区/县地图数据 2.2 获取乡/镇/街道地图数据 第一章 前言 需求:接到要做大屏的需求,其中需要用echarts绘画一个地图,但是需要的地图是区/县…...
Java_简单模拟实现ArrayList_学习ArrayList
文章目录 一、 了解线性表和顺序表区别1.线性表2.顺序表 二、模拟实现1.定义接口2.定义MyArrayList3.成员变量以及构造方法4.实现打印数组5.实现add方法6.实现查找某个数是否存在contains或者某个数的下标indexOf7.获取或更改pos位置的值 get和set8.获取数组大小 size9.删除某个…...
动手学深度学习(一)深度学习介绍2
目录 二、起源 三、深度学习的成功案例: 四、特点: 五、小结: 二、起源 为了解决各种各样的机器学习问题,深度学习提供了强大的工具。 虽然许多深度学习方法都是最近才有重大突破,但使用数据和神经网络编程的核心思…...
vmware网络配置,VMware的三种网络模式详解与配置
vmware为我们提供了三种网络工作模式 vmware为我们提供了三种网络工作模式, 它们分别是: Bridged(桥接模式)、NAT(网络地址转换模式)、Host-Only(仅主机模式)。 VMware虚拟机的三种网络类型的适用场景如下…...
【Ubuntu】安装hbase
前提 需要安装java 安装 HBase 下载并解压 HBase 安装包: wget https://dlcdn.apache.org/hbase/2.5.7/hbase-2.5.7-bin.tar.gz tar -zxvf hbase-2.5.7-bin.tar.gz配置 HBase 环境变量: export HBASE_HOME/path/to/hbase-2.5.7 export PATH$PATH:$H…...
ubuntu16.04环境轻松安装和应用opencv4.9.0(基于源码编译)
目录 一、环境准备 1、安装cmake 2、安装依赖 3、从github上下载opencv4.9.0.zip 二、安装opencv4.9.0 1、解压4.9.0.zip 2、进入build目录编译 3、安装编译好的相关库 4、修改opencv配置文件并使其生效 5、添加PKG_CONFIG路径,并使其生效 三、opencv环境…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
QT开发技术【ffmpeg + QAudioOutput】音乐播放器
一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
Django RBAC项目后端实战 - 03 DRF权限控制实现
项目背景 在上一篇文章中,我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统,为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...
【大模型】RankRAG:基于大模型的上下文排序与检索增强生成的统一框架
文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构C.1 指令微调阶段C.2 排名与生成的总和指令微调阶段C.3 RankRAG推理:检索-重排-生成 D 实验设计E 个人总结 A 论文出处 论文题目:RankRAG:Unifying Context Ranking…...
