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
的结构。
cpp
//单例模式
class PageCache
{
public:
//获取从对象到span的映射
Span* MapObjectToSpan(void* obj);
private:
std::unordered_map<PAGE_ID, Span*> _idSpanMap;
};
3. 如何建立页号和Span的对应关系?
建立页号和Span的关系。
cpp
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申请内存时,建立映射关系。
cpp
//获取一个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
。
cpp
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();
}