thread cache找central cache要内存,但central cache没有span或者没有空闲的span就会去下一层找page cache
分析问题:
1.通过页号计算页的起始地址,页号<<PAGE_SHIFT,PAGE_SHIFT设置为8*1024
2.span的起始地址=> void* start=(void*) (span->_pageId<<PAGE_SHIFT)
3.把span插到span桶,切分span,计算一个span的字节数size_t byte=span->_n<<PAGE_SHIFT ,将span大块内存切分成自由链表
4.page cache需要一把大锁,可能多个thread可能同时取page和插入新page,切page也会涉及不同的桶,所以central cache要几个字节就去对应的桶,直接使用桶锁,而page cache需要大锁,并且page cahce也是唯一存在的,需要单例模式
5.central cache没有空间的span,那就去找page cache 获取一个k页的span,page cache 没有就去找堆要,一找就找一个大的,因为大的才能连续,如果我想要2页的span没有,就去找大页切下来,大页切小,需要的自己用,不需要的接在page cache对应的桶链表后面,入锅page cache没有,就去找堆要128页的span,及2页给你用,126页接在page cache上
6.当thread cache太长满足一定条件以后,还给central cache 对应的span,当某个span 的use_count减为0,说明小内存全部回来,span还给page cache,还回来之后查一下页号,在page cache有空间的页就合并,page cache在不同的桶之间把大页切小,把小页合并
7.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; };