【lesson8】高并发内存池Central Cache层释放内存的实现

文章目录

  • Central Cache层释放内存的流程
  • Central Cache层释放内存的实现

Central Cache层释放内存的流程

当thread_cache过长或者线程销毁,则会将内存释放回central cache中的释放回来时–use_count。当use_count减到0时则表示所有对象都回到了span,则将span释放回page cachepage cache中会对前后相邻的空闲页进行合并。

但是这里就有一个问题了,我们如何知道还回来的对象属于哪个span?
其实 对象的地址/8K 就是对应span的地址 为什么呢?
画图理解
画两个span并写出_pageid和计算出地址
【lesson8】高并发内存池Central Cache层释放内存的实现_第1张图片
这时又两个对象1和2
假设:
1的地址:FA1000
2的地址:FA3000

除以8k看他们对应的span是否正确。
【lesson8】高并发内存池Central Cache层释放内存的实现_第2张图片
从图中可知,之前的结论确实正确。
所以我们接下来只要把_pageId和对应的span进行map映射即可,用map存储对应的_pageId和span
这个map我们设置在page cache中,因为映射关系的写入全部在page cache中进行。

#pragma once

#include "Common.h"

class PageCache
{
private:
	SpanList _spanLists[NPAGES];
	//对应的span
	std::unordered_map<PAGE_ID, Span*> _idSpanMap;
	PageCache()
	{}
	PageCache(const PageCache&) = delete;

	static PageCache _sInst;
};

而接下来就是建立映射关系,只要有一个span被申请出去,那么我们就要建立映射关系一次
而只有NewSpan可以申请span所以就要修改NewSpan

// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0 && k < NPAGES);

	// 先检查第k个桶里面有没有span
	if (!_spanLists[k].Empty())
	{
		Span* kSpan = _spanLists[k]->PopFront();
		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		//补充点1:画图补充便于理解
	    for (PAGE_ID i = 0; i < kSpan->_n; ++i)
		{
			_idSpanMap[kSpan->_pageId + i] = kSpan;
		}
		return kSpan;
	}

	// 检查一下后面的桶里面有没有span,如果有可以把他它进行切分
	for (size_t i = k+1; i < NPAGES; ++i)
	{
		if (!_spanLists[i].Empty())
		{
			Span* nSpan = _spanLists[i].PopFront();
			Span* kSpan = new Span;

			// 在nSpan的头部切一个k页下来
			// k页span返回
			// nSpan再挂到对应映射的位置
			kSpan->_pageId = nSpan->_pageId;
			kSpan->_n = k;

			nSpan->_pageId += k;
			nSpan->_n -= k;

			_spanLists[nSpan->_n].PushFront(nSpan);
			// 存储nSpan的首位页号跟nSpan映射,方便page cache回收内存时
			// 方便之后进行的合并查找
			// 补充点2:画图便于理解
			_idSpanMap[nSpan->_pageId] = nSpan;
			_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;


			// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
			for (PAGE_ID i = 0; i < kSpan->_n; ++i)
			{
				_idSpanMap[kSpan->_pageId + i] = kSpan;
			}

			return kSpan;
		}
	}

	// 走到这个位置就说明后面没有大页的span了
	// 这时就去找堆要一个128页的span
	Span* bigSpan = new Span;
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;

	_spanLists[bigSpan->_n].PushFront(bigSpan);

	return NewSpan(k);
}

补充点1

 for (PAGE_ID i = 0; i < kSpan->_n; ++i)
 {
	_idSpanMap[kSpan->_pageId + i] = kSpan;
 }

上面这段代码逻辑的具象化
【lesson8】高并发内存池Central Cache层释放内存的实现_第3张图片
补充点2

_idSpanMap[nSpan->_pageId] = nSpan;
_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;

上面代码是把Page Cache中还未使用的span放入map中进行映射以便page cache释放内存是合并
【lesson8】高并发内存池Central Cache层释放内存的实现_第4张图片
映射关系存入map中了,接下来Page Cache就要提供一个接口,来为我们查找是否有对应的映射

Span* PageCache::MapObjectToSpan(void* obj)
{
	//计算映射的页号
	PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
	//查找是否有该span,有则返回该span
	//只要没有,则一定是之前写的逻辑出现了问题
	auto ret = _idSpanMap.find(id);
	if (ret != _idSpanMap.end())
	{
		return ret->second;
	}
	else
	{
		assert(false);
		return nullptr;
	}
}

现在万事具备就只有实现Central Cache的释放流程了

Central Cache层释放内存的实现

void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanLists[index]._mtx.lock();
	while (start)
	{
		void* next = NextObj(start);
		
		//对象一个一个查找对应的映射关系
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		NextObj(start) = span->_freeList;
		span->_freeList = start;
		//还回来一个_useCount就减1
		//补充点1:_useCount++的逻辑
		span->_useCount--;

		// _useCount == 0说明span的切分出去的所有小块内存都回来了
		// 这个span就可以再回收给page cache,pagecache可以再尝试去做前后页的合并
		if (span->_useCount == 0)
		{
			//先把span从对应的桶中删除
			_spanLists[index].Erase(span);
			//然后清空span中无用的数据
			span->_freeList = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;

			// 释放span给page cache时,使用page cache的锁就可以了
			// 这时把桶锁解掉
			_spanLists[index]._mtx.unlock();
			
			//归还span给Page Cache,让它处理
			PageCache::GetInstance()->_pageMtx.lock();
			//调用Page Cache内的释放函数
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMtx.unlock();
			
			//恢复桶锁解掉
			_spanLists[index]._mtx.lock();
		}
		
		//走向下一个对象
		start = next;
	}

	_spanLists[index]._mtx.unlock();
}

补充点1:_useCount++的逻辑

// 从中心缓存获取一定数量的对象给thread cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanLists[index]._mtx.lock();

	Span* span = GetOneSpan(_spanLists[index], size);
	assert(span);
	assert(span->_freeList);

	// 从span中获取batchNum个对象
	// 如果不够batchNum个,有多少拿多少
	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;
	//实际拿了多少个usecount给Thread Cache就加多少
	span->_useCount += actualNum;

	_spanLists[index]._mtx.unlock();

	return actualNum;
}

你可能感兴趣的:(项目高并发内存池,C++,高并发内存池,C,多线程)