thread cache找central cache要内存,但central cache没有span或者没有空闲的span就会去下一层找page cache
分析问题:
通过页号计算页的起始地址,页号<<PAGE_SHIFT,PAGE_SHIFT设置为8*1024
span的起始地址=> void* start=(void*) (span->_pageId<<PAGE_SHIFT)
把span插到span桶,切分span,计算一个span的字节数size_t byte=span->_n<<PAGE_SHIFT ,将span大块内存切分成自由链表
page cache需要一把大锁,可能多个thread可能同时取page和插入新page,切page也会涉及不同的桶,所以central cache要几个字节就去对应的桶,直接使用桶锁,而page cache需要大锁,并且page cahce也是唯一存在的,需要单例模式
central cache没有空间的span,那就去找page cache 获取一个k页的span,page cache 没有就去找堆要,一找就找一个大的,因为大的才能连续,如果我想要2页的span没有,就去找大页切下来,大页切小,需要的自己用,不需要的接在page cache对应的桶链表后面,入锅page cache没有,就去找堆要128页的span,及2页给你用,126页接在page cache上
当thread cache太长满足一定条件以后,还给central cache 对应的span,当某个span 的use_count减为0,说明小内存全部回来,span还给page cache,还回来之后查一下页号,在page cache有空间的页就合并,page cache在不同的桶之间把大页切小,把小页合并
page cache 第一个桶挂一页的span,第二个桶挂两页的span,按页数映射,central cache需要几页就去几页的桶
申请内存:
1. 当central cache向page cache申请内存时,page cache先检查对应位置有没有span,如果没有 则向更大页寻找一个span,如果找到则分裂成两个。比如:申请的是4页page,4页page后面没 有挂span,则向后面寻找更大的span,假设在10页page位置找到一个span,则将10页page span分裂为一个4页page span和一个6页page span。
2. 如果找到_spanList[128]都没有合适的span,则向系统使用mmap、brk或者是VirtualAlloc等方式 申请128页page span挂在自由链表中,再重复1中的过程。
3. 需要注意的是central cache和page cache 的核心结构都是spanlist的哈希桶,但是他们是有本质 区别的,central cache中哈希桶,是按跟thread cache一样的大小对齐关系映射的,他的spanlist 中挂的span中的内存都被按映射关系切好链接成小块内存的自由链表。而page cache 中的 spanlist则是按下标桶号映射的,也就是说第i号桶中挂的span都是i页内存。
释放内存:
如果central cache释放回一个span,则依次寻找span的前后page id的没有在使用的空闲span, 看是否可以合并,如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span,减少 内存碎片。

确定需求之后就是代码的实现
thread向central cache获取一个非空的span,如果没有就去找page cache申请
// 获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
// 查看当前的spanlist中是否有还有未分配对象的span
Span* it = list.Begin();
while (it != list.End())
{
if (it->_freeList != nullptr)
{
return it;//找到了代表不用找page cache
}
else
{
it = it->_next;
}
}
//找不到空闲的span,需要向page cache申请
// 先把central cache的桶锁解掉,这样如果其他线程释放内存对象回来,不会阻塞
list._mtx.unlock();
//这里是获取page cache span,假设已经获取到了
PageCache::GetInstance()->_pageMtx.lock();
Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
PageCache::GetInstance()->_pageMtx.unlock();
// 对获取span进行切分,不需要加锁,因为这会其他线程访问不到这个span
// 计算span的大块内存的起始地址和大块内存的大小(字节数)
char* start = (char*)(span->_pageId << PAGE_SHIFT);
size_t bytes = span->_n << PAGE_SHIFT;
char* end = start + bytes;
// 把大块内存切成自由链表链接起来
// 1、先切一块下来去做头,方便尾插
span->_freeList = start;
start += size;
void* tail = span->_freeList;
int i = 1;
while (start < end)
{
++i;
NextObj(tail) = start;
tail = NextObj(tail); // tail = start;
start += size;
}
// 切好span以后,需要把span挂到桶里面去的时候,再加锁
list._mtx.lock();
list.PushFront(span);
return span;
}
计算一次向系统获取几页
// 一次thread cache从中心缓存获取多少个
static size_t NumMoveSize(size_t size)
{
assert(size > 0);
// [2, 512],一次批量移动多少个对象的(慢启动)上限值
// 小对象一次批量上限高
// 小对象一次批量上限低
int num = MAX_BYTES / size;
if (num < 2)
num = 2;
if (num > 512)
num = 512;
return num;
}
static size_t NumMovePage(size_t size)
{
size_t num = NumMoveSize(size);
size_t npage = num*size;
npage >>= PAGE_SHIFT; //*8*1024
if (npage == 0)
npage = 1;
return npage;
}
获取一个k页的span
PageCachePageCache::_sInst;//单例模式
// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{
assert(k > 0 && k < NPAGES);
// 先检查第k个桶里面有没有span
if (!_spanLists[k].Empty())
{
return _spanLists->PopFront();
}
// 检查一下后面的桶里面有没有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);
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);
}
//pagecache.h
class PageCache
{
public:
static PageCache* GetInstance()
{
return &_sInst;
}
Span* NewSpan(size_t k);
std::mutex _pageMtx;
private:
SpanList _spanLists[NPAGES];
PageCache()
{}
PageCache(const PageCache&) = delete;
static PageCache _sInst;
};