当前位置: 首页 > article >正文

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

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:项目日记-高并发内存池⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你做项目
  🔝🔝
开发环境: Visual Studio 2022


在这里插入图片描述

项目日记

  • 1. 前言
  • 2. 什么是内存碎片问题?
  • 3. 地址空间上的内存使用情况
  • 4. 页缓存合并内存的代码实现
  • 5. 总结以及对代码的拓展

1. 前言

请先看完页缓存的具体实现(上)

本章重点:

本篇文章着重讲解页缓存是怎样把从中心缓存中还回来的内存挂在桶上,并且进行前后页的内存合并的,合并内存形成更大的一份内存来减少内存碎片的问题


2. 什么是内存碎片问题?

我们拿整个程序地址空间来举例:

在这里插入图片描述

可以看见虽然整个程序地址空间还有300多byte的空间,但是要申请300byte却申请不出来,对于我们这个项目来说,假设有两个在地址空间中相邻的span,一个是10页,一个是15页,分别挂在第10号桶和第15号桶中,假设没有合并内存此时外部申请一个20页的span是申请不出来的,即使现在空闲的空间有25页,所以这也就体现出了这个项目中合并内存的重要性!


3. 地址空间上的内存使用情况

在地址空间中,一共是4GB大小的空间.
地址从0000 0000到FFFF FFFF.

第0页的起始地址是0
第一页的起始地址是8*1024KB
...以此类推

在这里插入图片描述

当中心缓存还回来一个span后,假设这个span中的大页内存在地址空间中是在第1000页,并且span的大小是50页,那么这个还回来的span就处于空闲状态了,此时我们只需要去检查地址空间中第999页的内存是否空闲,如果空闲就将1000~1050页和999页合并,形成一个51页的大块儿span,并且要不断向前合并直到遇见正在使用的页.同理,需要检查地址空间的1051页是否空闲,如果是空闲的,那么就将它一起合并过去!

在这里插入图片描述


4. 页缓存合并内存的代码实现

在pagecache.h文件中:

void PageCache::ReleaseSpanToPageCache(SpanData* span)
{
	if (span->_n > N_PAGES - 1)//大于128页的内存直接还给堆,不需要走pagecache
	{
		void* ptr = (void*)(span->_pageid << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);
		return;
	}
	//对span前后的页尝试进行合并,缓解外碎片问题
	while (1)//不断往前合并,直到遇见不能合并的情况
	{
		PAGE_ID prevId = span->_pageid - 1;
		auto prevret = _idSpanMap.find(prevId);
		if (prevret == _idSpanMap.end())//前面没有页号了
			break;
		SpanData* prevspan = ret;
		if (ret == nullptr)
			break;
		if (prevspan->_isUse == true)//前面的页正在使用
			break;
		if (prevspan->_n + span->_n > N_PAGES - 1)//当前页数加上span的页数大于128了,pagecache挂不下了
			break;
		//开始合并span和span的前面页
		span->_pageid = prevspan->_pageid;
		span->_n += prevspan->_n;
		_spanList[prevspan->_n].Erase(prevspan);//将被合并的页从pagecache中拿下来
		//delete prevspan;//将prevspan中的数据清除,诸如页号,页数等
		_spanPool.Delete(prevspan);
	}
	while (1)//不断往后合并,直到遇见不能合并的情况
	{
		PAGE_ID nextId = span->_pageid + span->_n;
		auto nextret = _idSpanMap.find(nextId);
		if (nextret == _idSpanMap.end())//前面没有页号了
			break;
		//SpanData* nextspan = nextret->second;
		auto ret = (SpanData*)_idSpanMap.get(nextId);
		if (ret == nullptr)
			break;
		SpanData* nextspan = ret;
		if (nextspan->_isUse == true)//前面的页正在使用
			break;
		if (nextspan->_n + span->_n > N_PAGES - 1)//当前页数加上span的页数大于128了,pagecache挂不下了
			break;
		//开始合并span和span的前面页
		span->_n += nextspan->_n;
		_spanList[nextspan->_n].Erase(nextspan);//将被合并的页从pagecache中拿下来
		//delete nextspan;//将prevspan中的数据清除,诸如页号,页数等
		_spanPool.Delete(nextspan);
	}
	//合并完后将span挂起来
	_spanList[span->_n].PushFront(span);
	//合并完后,要重新将这个span的首尾两页的id和这个span进行映射,方便别的span来合并我的时候使用
	_idSpanMap[span->_pageid] = span;
	_idSpanMap[span->_pageid + span->_n - 1] = span;
	span->_isUse = false;
}

对于代码的解释都在注释当中,大家可以发现整个合并内存的过程中,我们已经将delete操作符替换为了定长池中的free,这也就是完全脱离了free函数,并且当合并后的页数大于了128,此时整个页缓存哈希桶是挂不下的,所以要特别注意这一种情况


5. 总结以及对代码的拓展

页缓存结构的讲解已经结束,现在回头来看前面设计的这三层缓存结构,可谓是非常之巧妙,第一层线程缓存是无锁的,申请/释放内存非常高效,而第二层中心缓存是用的桶锁,在大多数情况下也没有竞争锁的问题,效率也非常高,所以现在能理解为什么要设计三层而不是两层,甚至是一层,一方面是为了效率的考量,另一方面是为了可以方便合并相邻的空闲页

对代码的拓展:

在使用到了直接向系统返还内存的函数:

inline static void SystemFree(void* ptr)
{
#ifdef _WIN32
	VirtualFree(ptr, 0, MEM_RELEASE);
#else
	// sbrk unmmap等
#endif
}

同样,这份代码知道就行了,不需详谈


🔎 下期预告:项目的测试以及优化🔍

http://www.kler.cn/a/227374.html

相关文章:

  • Go 中如何打印结构体?代码调试效率提升
  • 连接区块链节点的 JavaScript 库 web3.js
  • 幻兽帕鲁服务器游戏怎么升级版本?
  • 『运维备忘录』之 Cron 命令详解
  • vue实现二维数组表格渲染
  • ReentrantLock相较于synchronized有哪些区别(一)?
  • 【lesson9】高并发内存池Page Cache层释放内存的实现
  • 【BBF系列协议】TR181-2 TR369的设备数据模型
  • mac电脑风扇控制软件:Macs Fan Control Pro for mac 激活版
  • 【C++】拷贝构造函数和赋值运算符重载详解
  • Spring Boot 中操作 Bean 的生命周期
  • Unity3D 如何获取动态生成的物体的数据详解
  • 嵌入式学习第三篇——51单片机
  • ES6-let
  • “超越摩尔定律”,存内计算走在爆发的边缘
  • DS18B20温度传感器
  • 18.通过telepresence调试部署在Kubernetes上的微服务
  • Vue.js设计与实现(霍春阳)
  • 【Vue】组件间通信的7种方法(全)
  • JDK9~17部分新特性浅学