【项目日记(七)】第三层: 页缓存的具体实现(上)

news2024/10/5 21:13:32

💓博主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页的span
	SpanData* NewSpan(size_t k);
	std::mutex _mtx;//pagecache不能用桶锁,只能用全局锁,因为后面可能会有大页被切割为小页

	// 获取从对象到span的映射,给我一个地址,返回这个地址对应的span
	SpanData* MapObjectToSpan(void* obj);

	// 释放空闲span回到Pagecache,并合并相邻的span
	void 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结构,要经过下面几步:

  1. 根据中心缓存的桶号来确定申请的span是几页的
  2. 根据中心缓存想要申请的页数,找到页缓存中对应的桶(k页对应k号桶)
  3. 情况一: 页缓存的K号桶中存在span结构,直接将这块儿内存返回给中心缓存
  4. 情况二: 页缓存的K号桶没有span结构,但是K+1到128号桶中存在span结构,假设n号桶有span,则将这个大页的span切分为一个k页的span和一个n-k页的span,k页的span返回给中心缓存去使用,而将n-k页的span重新挂在n-k号桶中
  5. 情况三: 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就往后找去分裂大span
	assert(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,拿出来分裂成两个小span
	for (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页切分给KSpan
			ISpan->_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回收小块内存时查看哪块内存在哪块span
			for (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 _WIN32
	void* ptr = VirtualAlloc(0, kpage << PAGE_SHIFT, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	// linux下brk mmap等直接向系统申请内存的方式
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();
	return ptr;
}

🔎 下期预告:页缓存的具体实现(下)🔍

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

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

相关文章

Armv8-M的TrustZone技术之在安全状态和非安全状态之间切换

Armv8-M安全扩展允许在安全和非安全软件之间直接调用。 Armv8-M处理器提供了几条指令来处理状态转换: 下图显示了安全状态转换。 如果入口点的第一条指令是SG且位于非安全可调用内存位置中,则允许从非安全到安全软件的直接API函数调用。 当非安全程序调用安全API时,API通过…

Linux系统管理和Shell脚本笔试题

1、写一个sed命令&#xff0c;修改/tmp/input.txt文件的内容&#xff0c;要求&#xff1a;(1) 删除所有空行&#xff1b;(2) 在非空行前面加一个"AAA"&#xff0c;在行尾加一个"BBB"&#xff0c;即将内容为11111的一行改为&#xff1a;AAA11111BBB #写入内…

JavaWeb前端——HTML/CSS

HTML/CSS概述 HTML&#xff1a;学习标签&#xff0c;CSS&#xff1a;学习样式 HTML 1. 不区分大小写。 2. 属性可以使用单引号/双引号 3. 在记事本/编辑器中编写html语言&#xff0c;通过浏览器解析渲染语言 4. 语法结构松散&#xff08;编写时要尽量严谨&#xff09; VSc…

github请求超时解决方法

github请求超时解决办法 我使用windows执行如下git命令,提示超时 git clone xxxxx命令行提示如下&#xff1a; Failed to connect to github.com port 443: Timed out问题排查 可我Chrome可以正常访问github甚至ChatGPT&#xff0c;但是为什么在命令行里面却无法访问&#…

AI大模型开发架构设计(7)——人人都需要掌握的AI编程及应用案例实战

文章目录 人人都需要掌握的AI编程及应用案例实战1 AI代码生成模型与AI编程助手介绍程序设计方式的发展自动代码生成AI编程工具 2 AI编程助手的代码生成模型架构剖析以 CodeGeeX 为例-发展过程以 CodeGeeX 为例-训练过程以 CodeGeeX 为例-大规模代码数据处理以 CodeGeeX 为例-模…

『C++成长记』string使用指南

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、string类介绍 二、string类的常用接口说明 &#x1f4d2;2.1string类对象的常…

线下店铺的商品如何查价

品牌渠道主要分为线上和线下&#xff0c;线上的价格查价方式可以通过系统完成&#xff0c;系统筛选不同平台的数据&#xff0c;然后做价格的比较&#xff0c;输出是否破价的监测报表&#xff0c;这个过程多依赖系统去操作&#xff0c;但线下店铺的产品价格查价方式则没有固定的…

Windows下Cloudreve WebDAV挂载失败解决方法

如果你正在使用Windows的资源管理器挂载WebDAV&#xff0c;并且发现挂载失败&#xff0c;无论你如何去配置他 首先&#xff0c;Cloudreve 中可以为每个用户组单独设置“是否允许使用WebDAV访问”的开关&#xff0c;但是这玩意儿默认是开启的&#xff0c;所以如果不小心手残关掉…

机器学习系列——(六)数据降维

引言 在机器学习领域&#xff0c;数据降维是一种常用的技术&#xff0c;旨在减少数据集的维度&#xff0c;同时保留尽可能多的有用信息。数据降维可以帮助我们解决高维数据带来的问题&#xff0c;提高模型的效率和准确性。本文将详细介绍机器学习中的数据降维方法和技术&#…

解锁文档处理的全新维度:ONLYOFFICE 文档开发者版

前言 相信大家对于 ONLYOFFICE 这款办公软件可能已经有所耳闻&#xff0c;最近因工作需要&#xff0c;我在众多办公协作工具中选择了 ONLYOFFICE&#xff0c;原因主要是它开源经济实惠&#xff0c;可以部署在自己的服务器上并且能够轻松集成到我们的平台中。在数字化信息时代&…

10、数据结构与算法——堆

一、什么是堆 堆是一种特殊的树形数据结构&#xff0c;通常实现为完全二叉树或满二叉树。堆又分为两种类型最大堆&#xff08;Max Heap&#xff09; 和 最小堆&#xff08;Min Heap&#xff09; 1.1、什么是二叉树 二叉树是一种数据结构&#xff0c;它是由n&#xff08;n≥0&a…

Python 计算两个时间之间的耗时

博主按照自己的需求写的一个可以计算程序耗时的函数&#xff0c;各位有需要的自行改写哈 实现的大致功能 功能1 记录开始时间&#xff0c;返回一个时间字典。数据包括&#xff1a;开始时间的日期格式、文本格式 功能2 记录结束时间和耗时&#xff0c;返回一个时间字典。数据包…

Web3智能合约:重新定义商业合作的未来

随着区块链技术的飞速发展&#xff0c;Web3时代正逐渐到来&#xff0c;而其中的智能合约成为推动商业合作变革的关键力量。本文将深入探讨Web3智能合约的概念、特点以及对商业合作未来的巨大影响。 什么是Web3智能合约&#xff1f; 智能合约是一种以代码形式编写、自动执行合同…

Oracle 面试题 | 05.精选Oracle高频面试题

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Flask 入门4:Flask 模板

1. 前言 Flask 拥有丰富的扩展方法&#xff0c;且都有统一的特点&#xff1a;简单和即学即用。当我们要实现某个功能之前&#xff0c;可以提前去搜一搜这个功能包是否已经存在&#xff0c;这样也能帮助我剩下很多时间。那么要去哪里找到这些扩展包呢&#xff0c;这里推荐两个方…

Android:RecyclerView跨行跨列的LayoutManager:Spannedgridlayoutmanager

前言&#xff1a; RecyclerView可以使用GridLayoutManager实现跨行&#xff0c;但是不能跨列&#xff1b;瀑布流布局可以跨列但是又不能跨行。原生自带的各个LayoutManager中并没有可以又跨行又能跨列的。网上搜寻了一番&#xff0c;找到了一个亲测可行好用的三方库&#xff1…

微信小程序可以一个人开发么?

随着移动互联网的飞速发展&#xff0c;微信小程序已经成为了人们日常生活中不可或缺的一部分。它以其便捷性和高效性赢得了广大用户的青睐。对于许多有志于进入小程序领域的开发者来说&#xff0c;一个常见的问题是&#xff1a;微信小程序可以一个人开发吗&#xff1f;答案是肯…

2024 Flutter 重大更新,Dart 宏(Macros)编程开始支持,JSON 序列化有救

说起宏编程可能大家并不陌生&#xff0c;但是这对于 Flutter 和 Dart 开发者来说它一直是一个「遗憾」&#xff0c;这个「遗憾」体现在编辑过程的代码修改支持上&#xff0c;其中最典型的莫过于 Dart 的 JSON 序列化。 举个例子&#xff0c;目前 Dart 语言的 JSON 序列化高度依…

【笔记】React-Native Navigation页面导航

/** * 须知&#xff1a;a bare React Native Project(not an Expo managed project) * * React官方文档&#xff1a;https://reactnative.dev/docs/navigation * * 当前文档基于6.x * React Navigation文档&#xff1a;https://reactnavigation.org/docs/getting-started * …

C++ Webserver从零开始:基础知识(七)——多进程编程

前言 在学习操作系统时&#xff0c;我们知道现代计算机往往都是多进程多线程的&#xff0c;多进程和多线程技术能大大提高了CPU的利用率&#xff0c;因此在web服务器的设计中&#xff0c;不可避免地要涉及到多进程多线程技术。 这一章将简要讲解web服务器中的多进程编程&#x…