thread cache中自由链表过长后,会将多出来的内存还给entral cache.thread cache还回来的内存可能不属于同一个span,因此,除了计算出要还到central cache的哪个桶之外,还需要计算出还到桶的哪个span。
1. 如何根据对象的地址找到对象的页号?
页号 = 对象的地址 / 页的大小。
如果一个页的大小是100,那么第0 ~ 99都属于第0页。因为 0 ~ 99 除以100等于0.
100~199都属于第1页,因为100 ~ 199 除以100等于1.
2. 如何找到还回来的内存对应的Span?
我们可以根据还回来的内存找到对应的页号了,但是页号和
Span并没有对应关系,我们无法通过页号找到对应的Span。因此,需要建立一个Span和页号的对应关系。
将这个对应关系的接口建立在page cache中。下面是page cache的结构。
//单例模式
class PageCache
{
public:
//获取从对象到span的映射
Span* MapObjectToSpan(void* obj);
private:
std::unordered_map<PAGE_ID, Span*> _idSpanMap;
};
3. 如何建立页号和Span的对应关系?
建立页号和Span的关系。
Span* PageCache::MapObjectToSpan(void* obj)
{
//计算页号
PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
/*std::unique_lock<std::mutex> lock(_pageMtx);
auto ret = _idSpanMap.find(id);
if (ret != _idSpanMap.end())
{
return ret->second;
}
else
{
assert(false);
return nullptr;
}*/
//建立页号和SPan的关系
auto ret = (Span*)_idSpanMap.get(id);
assert(ret != nullptr);
return ret;
}
每当
page cache分配span给central cache时,都需要记录一下页号和span之间的映射关系。此后当thread cache还对象给central cache时,才知道应该具体还给哪一个span。
因此当
central cache在调用NewSpan接口向page cache申请k页的span时,page cache在返回这个k页的span给central cache之前,应该建立这k个页号与该span之间的映射关系。
因此需要在central cache向page cache申请内存时,建立映射关系。
//获取一个k页的span
Span* PageCache::NewSpan(size_t k)
{
assert(k > 0 && k < NPAGES);
//先检查第k个桶里面有没有span
if (!_spanLists[k].Empty())
{
Span* kSpan = _spanLists[k].PopFront();
//建立页号与span的映射,方便central cache回收小块内存时查找对应的span
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页下来
kSpan->_pageId = nSpan->_pageId;
kSpan->_n = k;
nSpan->_pageId += k;
nSpan->_n -= k;
//将剩下的挂到对应映射的位置
_spanLists[nSpan->_n].PushFront(nSpan);
//建立页号与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);
}
4 central cache内存回收
4.什么时候将central cache的内存还给page cache?
当
thread cache还对象给central cache时,依次遍历这些对象,将这些对象插入到其对应span的自由链表当中,更新该span的_usseCount计数。
在
thread cache还对象给central cache的过程中, 如果central cache中某个span的_useCount减到0,说明这个span分配出去的对象全部都还回来了,那么此时就可以将这个span再进一步还给page 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
Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
NextObj(start) = span->_freeList;
span->_freeList = start;
span->_useCount--;
// 说明span的切分出去的所有小块内存都回来了
// 这个span就可以再回收给page cache,pagecache可以再尝试去做前后页的合并
if (span->_useCount == 0)
{
//将这块Span从_spanLists中删除
_spanLists[index].Erase(span);
span->_freeList = nullptr;
span->_next = nullptr;
span->_prev = nullptr;
// 释放span给page cache时,使用page cache的锁就可以了
// 这时把桶锁解掉
_spanLists[index]._mtx.unlock();
PageCache::GetInstance()->_pageMtx.lock();
PageCache::GetInstance()->ReleaseSpanToPageCache(span);
PageCache::GetInstance()->_pageMtx.unlock();
_spanLists[index]._mtx.lock();
}
start = next;
}
_spanLists[index]._mtx.unlock();
}




















