高并发内存池项目(5)——实现PageCache
目录
一,PageCache简介
二,PageCache的具体实现
1,成员
2,函数
1,NewSpan函数实现
2,Span* MapObjectToSpan函数实现
3,ReleaseSpanToPage函数实现
三,总结
一,PageCache简介
PageCache是高并发内存池的第三层,也是最后一层。这一层便是真的产生内存的地方,在PageCache这一层会直接调用内存函数向计算机申请内存。申请内存时,一次性申请的内存会很大,主要是为了减少申请的次数。
实现的PageCache类如下:
class PageCache
{
public:static PageCache* GetInstance(){return &_sInstan;}//Newspan函数:Span* NewSpan(int page);// 获取从对象到span的映射Span* MapObjectToSpan(void* obj);//将从CentralCache释放回来的内存重新放到PageCache中void ReleaseSpanToPage(Span* span);private://禁掉拷贝,赋值,私有化构造PageCache(){}PageCache(const PageCache&) = delete;PageCache& operator = (const PageCache&) = delete;//定义一个span哈希表Spanlist _Spanlist[Npage];//定义一个单例对象static PageCache _sInstan;//页号和Span对象间的映射std::map<PAGEID,Span*>_IdMap;
public:std::mutex _mut;};
PageCache也实现了一个单例,也是为了在main函数调用前就先实例化一个PageCache对象。
二,PageCache的具体实现
1,成员
PageCache类里面,有四个成员。分别是:
Spanlist数组:这个数组的作用也是挂载内存,调用者通过内存对齐函数算出下标便可以找到对应span上面的内存,从而向该span上申请内存。同时,这里还有一个Nspan。这个Nspan的大小是129,所以这个Spanlist最多可以被申请128KB大小的空间。
IdMap哈希表(红黑树):这个成员主要记录了页号和Span之间的映射关系,主要是方便在归还内存时找到对应的Span。
_mut:这是一个互斥锁,主要是在高并发环境下使用,防止在高并发环境下造成错误。
2,函数
1,NewSpan函数实现
函数实现具体如下:
Span* PageCache::NewSpan(int page)
{//申请的内存页数大于最大的页if (page >= Npage){//向堆申请内存void* ptr = Pagemalloc(page);//Span* span = PageCache::GetInstance()->_Newtospan.New() ;Span* span = new Span;span->_pageid = ((PAGEID)ptr >> PageShift);span->_used = true;span->_n = page;//span->_objSize = page << PageShift;_IdMap[span->_pageid] = span;return span;}assert(page < Npage&& page>0);//开始查看对应的PageCache对应的下标下的spanlist的情况if (!_Spanlist[page].Empty()){return _Spanlist[page].PopFront();}//如果没有就向下遍历找下面的Page是否有非空链表for (int i = page + 1;i < Npage;i++){if (!_Spanlist[i].Empty()){//得到一个spanSpan* nspan = _Spanlist[i].PopFront();//Span* kspan = PageCache::GetInstance()->_Newtospan.New();Span* kspan = new Span;//开始切分nspankspan->_pageid = nspan->_pageid;kspan->_n += page;kspan->_used = true;nspan->_pageid += page;nspan->_n -= page;//将交给CentralCache的span建立好与PageId的映射PageCache::GetInstance()->_mut.lock();_IdMap[nspan->_pageid] = nspan;_IdMap[nspan->_pageid + nspan->_n] = nspan;for (int i = 0;i < kspan->_n;i++){_IdMap[kspan->_pageid+i] = kspan;}PageCache::GetInstance()->_mut.unlock();//重新找个位置放nspan_Spanlist[nspan->_n].PushFront(nspan);return kspan;}}//否则就向堆申请128页的空间//Span* span = PageCache::GetInstance()->_Newtospan.New();Span* span = new Span;void* ptr = Pagemalloc(Npage - 1);span->_n = Npage - 1;span->_pageid = (PAGEID)ptr >> PageShift;//span->_objSize = page << PageShift;_Spanlist[Npage - 1].PushFront(span);//再次递归调用一下:主要是为了复用代码逻辑return NewSpan(page);
}
在调用这个函数时会出现三种情况:
1,传入的Page大于Npage,这时便可以直接调用按照页来申请内存的库函数来申请相应的内存。
2,传入的Page大小小于Npage并且对应的_SpanList上刚好有内存,这时便可以直接返回对应的内存大小。
3,当对应的index下的Span下没有内存时, 但是比index大的下标下的span上有内存。这个时候便可以将这个内存n切分为k和n-k两段内存。这个n-k大小的内存会被重新插入回PageCache上的SpanList上。
4,当整个SpanList的Span上都没有内存时,就直接申请128KB大小的内存挂到下标为128的Spanlist中的span上。
2,Span* MapObjectToSpan函数实现
函数具体实现:
// 获取从对象到span的映射
Span* PageCache::MapObjectToSpan(void* obj)
{//加一把RAII的锁,使用这个函数时会自动加锁std::unique_lock<std::mutex> lock(_mut);PAGEID id = ((PAGEID)obj >> PageShift);auto it = _IdMap.find(id);if (it != _IdMap.end()){return it->second;}else{assert(false);return nullptr;}
这个函数实现的功能是传入对象的地址,计算出这个地址对应的页号,然后返回与页号对应的Span对象。
3,ReleaseSpanToPage函数实现
该函数具体实现代码如下:
void PageCache::ReleaseSpanToPage(Span* span)
{if (span->_n >= Npage){void* ptr = (void*)(span->_pageid << PageShift);Pagefree(ptr);//_Newtospan.Delete(span);delete span;return;}//在归还时为了减少外碎片问题可以先向前向后合并小块内存再归还到PageCache中while (1){//退出的条件:1,对应页号的span不存在,2,对应页号的span被CentralCache使用,3,归并两个span后的页数大于128PAGEID PreId = span->_pageid -1;auto it = _IdMap.find(PreId);if (it == _IdMap.end()){break;}Span* Prespan = it->second;if (Prespan->_used){break;}if (Prespan->_n + span->_n > Npage){break;}span->_pageid = Prespan->_pageid;span->_n += Prespan->_n;//_Newtospan.Delete(Prespan);delete Prespan;}while (1){PAGEID NextId = span->_pageid + span->_n;auto it = _IdMap.find(NextId);if (it == _IdMap.end()){break;}Span* Nextspan = it->second;if (Nextspan->_used){break;}if (span->_n + Nextspan->_n > Npage){break;}span->_n += Nextspan->_n;//_Newtospan.Delete(Nextspan);delete Nextspan;}//最后将合并好的span头插到_Spanlist中span->_used = false;_IdMap[span->_pageid] = span;_IdMap[span->_pageid + span->_n - 1] = span;_Spanlist->PushFront(span);
}
该函数实现的功能是在CentralCache上的某一段内存得_usecount等于0时,CentralCache便要调用这个函数来将这一段内存还给PageCache。
三,总结
通过以上得函数实现便可以实现一个PageCache类,这个PageCache类实现了内存申请和释放等功能。在实现完ThreadCache,CentralCache,PageCache等功能后便可以开始测试并进行优化。