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

1. 前言

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


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

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

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


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

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

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

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


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;
}

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

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

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

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

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


02-04 12:08