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

CentralCache

目录

一、Span和Spanlist

二、CentralCache


一、Span和Spanlist

        CentralCache其实也是哈希桶结构,只不过他是一个个的Span(Span是管理一定数量的页的结构),而且Span会管理一个freelist,用来挂起一个个的小内存块给ThreadCache使用

        同一个SpanList桶中的Span结构必须相同,除了freelist可以有不同的挂载情况

SpanList的结构

        这其中有一个字段叫做ustCount,他记录的就是该Span中的小内存块的使用情况,每给一个到ThreadCache,该字段就++,当ThreadCache调用ListTooLong归还内存块到CentralCache的时候再--。

        一旦useCount被减到0了,则把该Span返还给PageCache。从而缓解内存碎片的问题。

当然CentralCache刚刚获取到Span的时候,useCount一定是0,那么万一此时刚好处于归还逻辑呢?岂不是又还回去了,白申请了。其实在Span中还有一个isuse表示是否正在被使用,就解决了这个问题。

//Span管理以页为单位的大块内存
struct Span
{
	PAGE_ID _pageId = 0;	//起始页的页号
	size_t _pageN=0;		//页的数量

	size_t _useCount=0;		//Span中有多少个小内存块被分配给ThreadCache了
	void* _freelist=nullptr;			//切好的小块内存块的自由链表,方便下一次分配给ThreadCache,不用在分配的时候切了,就好像定长池的memory

	bool _isuse=false;		//是否正在使用

	size_t _objSize = 0;	//小内存块的大小,即freelist中挂的一个个小对象节点的大小

	Span* _prev = nullptr;
	Span* _next = nullptr;
};

//带头双向循环链表
class SpanList
{
public:
	SpanList()
	{
		_head = new Span();
		_head->_next = _head;
		_head->_prev = _head;
	}

	bool Empty()
	{
		return _head->_next == _head;
	}

	Span* PopFront()
	{
		Span* front = _head->_next;
		Erase(front);
		return front;
	}

	void PushFront(Span* span)
	{
		Insert(Begin(),span);
	}

	Span* Begin()
	{
		return _head->_next;
	}
	Span* End()
	{
		return _head;
	}

	void Insert(Span* pos,Span* newSpan)
	{
		assert(pos);
		assert(newSpan);
		//前Span
		Span* prev = pos->_prev;
		prev->_next = newSpan;
		newSpan->_prev = prev;
		newSpan->_next = pos;
		pos->_prev = newSpan;
	}

	void Erase(Span* pos)
	{
		assert(pos);
		assert(pos!=_head);
		//前后Span
		Span* next = pos->_next;
		Span* prev = pos->_prev;
		prev->_next = next;
		next->_prev = prev;
		
	}
	
	std::mutex& GetMutex()
	{
		return _mutex;
	}

private:
	Span* _head=nullptr;
	std::mutex _mutex;		//桶锁
};

二、CentralCache

#pragma once
#include"Common.hpp"
#include"PageCache.hpp"

//单例模式---饿汉
class CentralCache
{
public:
	static CentralCache* GetInstance()
	{
		return &_sInstance;
	}
	// 从中心缓存获取一定数量的对象给thread cache
	size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);

	// 获取一个非空的span
	Span* GetOneSpan(SpanList& list, size_t byte_size);

	// 将一定数量的对象释放到span跨度
	void ReleaseListToSpans(void* start, size_t byte_size);

private:
	static CentralCache _sInstance;

	CentralCache() {}
	CentralCache(const CentralCache&) = delete;
private:
	SpanList _spanlists[NFREELISTS];

};

//inline防止多个源文件包含导致重复定义
inline CentralCache  CentralCache::_sInstance;


void CentralCache::ReleaseListToSpans(void* start, size_t byte_size)
{
	//通过index可以知道该内存块是属于哪一个SpanList的
	size_t index = SizeClass::Index(byte_size);
	_spanlists[index].GetMutex().lock();
	while (start!=nullptr)
	{
		void* next = NextObj(start);
		//1.找到该内存块是属于哪一个Span
		//当然可以通过内存块的地址,计算出页号,然后和SpanList中的Span的页号一个个比较,但是时间复杂度较高,采取哈希表映射
		
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		
		//2.把该内存块头插到Span的freelist中
		NextObj(start) = span->_freelist;
		span->_freelist = start;
		span->_useCount--;
		if (span->_useCount==0)
		{
			//该Span已经没有人用了,归还到PageCache中
			_spanlists[index].Erase(span);
			span->_freelist = nullptr;
			span->_next = span->_prev = nullptr;

			_spanlists[index].GetMutex().unlock();
			
			PageCache::GetInstance()->GetMutex().lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->GetMutex().unlock();
			
			_spanlists[index].GetMutex().lock();

		}

		//一个内存块还回去了,往后走一步
		start = next;
	}

	_spanlists[index].GetMutex().unlock();
}


Span* CentralCache::GetOneSpan(SpanList& list, size_t byte_size)
{
	//1.CentralCache有非空Span,直接取
	Span* it = list.Begin();
	while (it!=list.End())
	{
		if (it->_freelist!=nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}

	//把CentralCache的桶锁解开,其他线程释放内存的时候,就不会阻塞了
	list.GetMutex().unlock();

	//2.如果CentralCache中没有,找PageCache要
	PageCache::GetInstance()->GetMutex().lock();
	Span* span=PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(byte_size));
	span->_objSize = byte_size;
	span->_isuse = true;
	PageCache::GetInstance()->GetMutex().unlock();


	//3.把从PageCache中要到的Span,切分成小的内存块,挂到Span的freelist中
	char* start = (char*)((span->_pageId) << PAGE_SHIFT);		//起始地址(为什么pageId就是系统中的页号呢?为什么系统调用堆是从0页开始呢?)
	size_t bytes = (span->_pageN) << PAGE_SHIFT;			//总字节数
	char* end = start + bytes;
	//先头插一个
	span->_freelist = start;
	start += byte_size;
	void* tail = span->_freelist;
	//再把剩下的插入
	while (start<end)
	{
		//尾插
		NextObj(tail) = start;
		tail = NextObj(tail);
		start += byte_size;
	}
	NextObj(tail) = nullptr;

	//插入到SpanList的时候,需要加锁,因为插入的动作是临界资源
	list.GetMutex().lock();
	list.PushFront(span);


	return span;
}



size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);
	//加锁
	_spanlists[index].GetMutex().lock();
	//1.获取从非空的Span,从这个Span中获取自由链表中的内存块节点
	Span* span = GetOneSpan(_spanlists[index],size);
	assert(span);
	assert(span->_freelist);

	start = span->_freelist;
	end = start;
	size_t i = 0;
	size_t actualNum = 1;
	while(i<batchNum-1 && NextObj(end)!=nullptr)
	{
		end = NextObj(end);
		i++;
		actualNum++;
	}
	span->_freelist = NextObj(end);
	NextObj(end)=nullptr;

	//2.修改Span中的使用情况
	span->_useCount += actualNum;


	//解锁
	_spanlists[index].GetMutex().unlock();
	return actualNum;
}


        值得注意的是:获取到span后我们要通过这个span的页数来知道这个span有多少内存,并且要通过这个span在程序地址空间的页号来判断这份内存的起始地址是多少!第0页的地址是0000 0000,第一页的地址是8KB,以此类推。

        为什么从PageCache拿出Span给CentralCache的时候,需要建立一个页号和Span的映射关系呢?

        因为PageCache的桶结构仍然是SpanList,但是和CentralCache不同的是:PageCache的桶中的Span是可以被修改的,即可以被切分成小的Span;而CentralCache的Span你开始的时候拿到的是多大的页,就永远是那么大了。

        ThreadCache的内存块不被使用后,归还的时候可以计算出页号(页号通过小内存块的ptr指针计算)到了CentralCache,发现这个Span在不被使用后,归还给PageCache。而PageCache又会检查我的前后Span有没有归还,如果归还了,则可以把这两个Span合并成一个大的Span,从PageCache的若干个小桶移动到一个大桶中,从而缓解外碎片的问题。

相关文章:

  • 登录窗口布局
  • 具身智能零碎知识点(一):深入解析Transformer位置编码
  • oracle 包的管理
  • ffmpeg提取字幕
  • 八大排序——c++版
  • 如何使用 Coze 的 HTTP 请求节点实现高效数据交互
  • 《深度揭秘:借助MySQL实现AI模型训练全程追溯》
  • 数据驱动金融韧性升级,开启数据交换“新范式”:构建“实时、国产化强适配”的数据交换与共享平台
  • java基础使用- 泛型
  • 《DeepSeek RAG 增强检索知识库系统》Ollama DeepSeek 流式应答页面对接之三
  • Postgres数据库源码编译及部署
  • 【11408学习记录】英语语法核心突破:揭秘表语从句结构与通知写作实战技巧
  • 数据结构与算法:基础与进阶
  • 5分钟上手GitHub Copilot:AI编程助手实战指南
  • 【大模型】DeepSeek+蓝耕MaaS平台+海螺AI生成高质量视频实战详解
  • TDengine JAVA 语言连接器
  • Ai云防护技术解析——服务器数据安全的智能防御体系
  • 安卓玩机工具-----安卓机型通用 无损备份与恢复数据的工具BackupToolkit 操作过程
  • 26届Java暑期实习面经,腾讯视频一面
  • 单例模式的写法(保证线程安全)
  • 专题学习网站开发流程/网络营销和传统营销的关系
  • 百度搜索入口官网/网络推广seo公司
  • 响应式网站是做多大尺寸/公司建网站多少钱
  • 巩义做网站汉狮网络/创网站永久免费建站
  • 做网站用的笔记本配置/最近热点新闻事件
  • 璧山网站建设/怎么提高百度搜索排名