目录
一、Thread Cache的回收实现
1.1Thread Cache回收框架
在实现完整的高并发内存池内存分配逻辑以后,回收逻辑就变得清晰明了很多了,怎么分出去的就怎么收回来,不过相比较与分配逻辑,回收则是从小块内存合并到大块内存最后再次挂入Page Cache进行下次使用,本文博主对Thread Cache的实现进行了详细的拆分及讲解。
因为小块内存的使用频率相比于大块内存是更多的,而Thread Cache也是直接面对用户的第一层,所以在大逻辑上,当用户要释放数据时,就先调用Thread Cache的释放函数:
这里只是先对释放的逻辑进行初步实现,针对超大内存的申请,博主会在后面对代码进行优化。
static void ConcurrentFree(void* ptr,size_t size)//释放
{
assert(pTLSThreadCache);
pTLSThreadCache->Deallocate(ptr,size);
}
Thread Cache释放内存遵循以下两个特点:
1. 当释放的内存小于256KB时将内存释放回thread cache中,计算size所映射的自由链表桶组成的_freelist中的位置下标index,然后将对象Push 到_freelists[index]中。
2. 当哈希桶中某个_freelist链表的长度过长,空闲内存块个数达到阈值(一般将_MaxSize也就是每个链表中单次的最大申请数作为这个值),则回收一部分内存对象到Central Cache中。
1.2Thread Cache回收实现
void ThreadCache::Deallocate(void* ptr, size_t size)//释放空间,顺便收集内存碎片返还给central cache
{
assert(ptr);
assert(size <= MAX_BYTES);
//size_t alignSize = SizeClass::RoundUp(size);
//还回来时不需要再去做对齐,因为申请时申请到的就是对齐完毕的,释放时肯定是桶内存在的
//去找所要释放的size所在的桶,然后将要释放的对象进行Push插入
size_t index = SizeClass::Index(size);
_freelist[index].Push(ptr);
//当一个桶内链表长度大于一次批量申请的内存时就切一段list还给central cache
if (_freelist[index].Size() >= _freelist[index].MaxSize())
{
ListTooLong(_freelist[index], size);
}
}
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
void* start = nullptr;
void* end = nullptr;
list.ringpop(start, end, list.MaxSize());
CentralCache::GetInstance()->ReleaseListToSpans(start, size);//将当前进程中有大量空闲内存的freelist切出一部分还给Central Cache
}
Thread Cache中的回收逻辑分为两部分:
1、将小块内存放到对应的桶中的Dellocate
2、当桶中内存块达到一定数量后将其放回Central Cache中。
二、Central Cache
2.1Central Cache回收框架
1、当Thread_cache中某个桶出现内存过长或者线程退出销毁,则会将内存释放回central cache同样的下标所对应的桶中,释放回来时通过对链表中一个个内存块所算出的页号的查找,对应到具体的Span中,并将该Span中的use_count--。
2、当use_count减到0时则表示所有对象都回到了span,这时则将span重新放回回page cache, 因为Span中存储的内存大小都是整数页,所以在page cache中会对前后相邻的空闲页进行合并。
2.2Central Cache回收实现
将来自某个Thread Cache中空闲的内存块链表接收以后,找到和其大小相对应的桶后,对其地址计算页号,找到该桶中对应的Span,然后将其头插到Span中的freelist之中,再将该Span的usecount--,然后去处理链表中的下一个内存块,直到将整个链表中的内存块都放到对应的Span当中。
当Span中的usecount为0时,就要将该Span从对应的桶中删除(并不是释放了只是从Central Cache中删除),将Span中的数据都清理干净,然后将Span再交给Page Cache的回收函数对其进行前后空闲页合并和回收。
//回收从Thread Cache传回来的内存
void CentralCache::ReleaseListToSpans(void* start, size_t size)//strat:传回来链表的起始地址
{
size_t index = SizeClass::Index(size);
//计算出从此处thread Cache中传回的批量内存块属于central cahce中的哪个spanlist后,
//对其进行加锁,将内存放回去
_spanList[index]._mtx.lock();
while (start)
{
void* next = NextObj(start);
//关于为什么要查找页号才能确认span,因为虽然我们可以确认当前size在central cache的哪个桶
//但是每个桶中有大量的span,如果一个一个span去便利对比根据地址计算后的页号
//有n个span,每个sapn有n个切好的小内存,复杂度直接n方了铁铁,所以直接在pagecache切分span时就建立页号和某一具体span的地址
Span* span = PageCache::GetInstance()->MapObjectToSpan(start);//查找当前内存块所对应的页号
NextObj(start) = span->_freeList;
span->_freeList = start;
span->_useCount--;//将span中记录已经使用的_usecount--
//当这个span中的usecount=0时,说明当前span切分出去的小块内存全部回来了
//此时就可以将当前span回收给page cache
if (span->_useCount == 0)
{
_spanList[index].Erase(span);
span->_freeList = nullptr;
span->_next = nullptr;
span->_prev = nullptr;
//span->_isUse=false;不能在这里直接置为false,如果此时再有其他线程归还,前后合并时
//如果合并到这里的Span,就会出现大问题
//此时这个span已经和central cache的spanlist完全断开了连接
//直接解锁允许其他线程继续访问当前桶
_spanList[index]._mtx.unlock();
//去调用Page Cache的大锁
PageCache::GetInstance()->_pageMtx.lock();
PageCache::GetInstance()->ReleaseSpanToPageCache(span);
PageCache::GetInstance()->_pageMtx.unlock();
_spanList[index]._mtx.lock();//重新上锁,去处理下一个小块内存
}
start = next;//继续去处理下一个threadcache传回来的内存块
}
_spanList[index]._mtx.unlock();
}
标签:span,Thread,Cache,内存,Central,Span,size
From: https://blog.csdn.net/2201_75880188/article/details/140754347