19互动网站建设,网络营销管理,企业网站翻译,哪个网站可以做英语语法题高并发内存池 1. 释放内存1.1 thread cache1.2 central cache1.3 page cache 2. 申请和释放剩余补充 点赞#x1f44d;#x1f44d;收藏#x1f31f;#x1f31f;关注#x1f496;#x1f496; 你的支持是对我最大的鼓励#xff0c;我们一起努力吧!#x1f603;#x… 高并发内存池 1. 释放内存1.1 thread cache1.2 central cache1.3 page cache 2. 申请和释放剩余补充 点赞收藏关注 你的支持是对我最大的鼓励我们一起努力吧! 1. 释放内存
1.1 thread cache
释放内存
当释放内存小于256k时将内存释放回thread cache计算size映射自由链表桶位置i将对象Push到_freelists[i]。当链表的长度过长则回收⼀部分内存对象到central cache。
//ConcurrentAlloc.h
static void ConcurrentFree(void* ptr,size_t size)
{assert(pTLSThreadCache);pTLSThreadCache-Deallocate(ptr, size);
}以什么标准来判断ThradCache当前某个桶的自由链表的长度过长了呢
我们在给ThreadCache的桶加一个Size成员记录当前这个桶空闲内存块的个数。如果当前这个桶空闲内存块的个数大于等于这个桶下一次找CentralCache要的一批量的内存块个数。那就把这一批量从当前桶拿到然后还给CentralCache。
其实还可以给ThreadCache一个记录它占了多大内存的成员如果这个遍历大于2M就从上到下遍历桶还一部分空闲内存块回去避免一个ThreadCache占据太多内存。我们这个项目的原型考虑了更多这里的细节。我们就简单一下用个Size记录当前桶空闲内存块个数就行了。
//Common.hstatic void* NextObj(void* obj)
{return *(void**)obj;
}class Freelist
{
public://释放内存void Push(void* obj){//头插assert(obj);NextObj(obj) _freelist;_freelist obj;_size;}//申请内存void* Pop(){//头删assert(_freelist);void* obj _freelist;_freelist NextObj(obj);--_size;return obj;}//一次挂一批void PushRange(void* start,void* end,size_t n){NextObj(end) _freelist;_freelist start;_size n;}//一次拿一批void PopRange(void* start, void* end, size_t n){assert(_size n);start end _freelist;for (size_t i 0; i n - 1; i){end NextObj(end);}_freelist NextObj(end);NextObj(end) nullptr;_size - n;}//链表是否为空bool IsEmpty(){return _freelist nullptr;}//获取当前桶获取下一批内存块的个数size_t GetMaxSize(){return _maxSize;}//当前桶的空闲内存块的个数size_t GetSize(){return _size;}private:void* _freelist nullptr;size_t _maxSize 1;//记录当前桶下一次找ContraltCache一次要一批量是多少个内存对象size_t _size 0;//当前桶空闲内存块个数
};//ThreadCache.hclass ThreadCache
{
public://申请和释放内存对象void* Allocate(size_t size);void Deallocate(void* ptr, size_t size);// 从中心缓存获取对象void* FetchFromCentralCache(size_t index, size_t size);// 释放对象时链表过长时回收内存回到中心缓存void ListTooLong(Freelist list, size_t size);
private:Freelist _freelists[NFREELIST];
};// TLS thread local storage 线程局部存储
static _declspec(thread) ThreadCache* pTLSThreadCache nullptr;//ThreadCache.hppvoid ThreadCache::Deallocate(void* ptr, size_t size)
{assert(ptr);assert(size MAX_BYTES);size_t index SizeClass::Index(size);_freelists[index].Push(ptr);//当前桶空闲内存块个数大于等于下一次申请一批量内存块个数//就还下一批量内存块的个数给CentralCacheif (_freelists[index].GetSize() _freelists[index].GetMaxSize()){//从当前桶获取一批量内存块ListTooLong(_freelists[index], size);}
}void ThreadCache::ListTooLong(Freelist list, size_t size)
{void* start nullptr;void* end nullptr;//获取一批量内存块对象list.PopRange(start, end, list.GetMaxSize());//将这一批内存块对象还给CentralCache对应桶下的对应Span对象CentralCache::GetInstance()-ReleaseListToSpans(start, size);
}1.2 central cache
释放内存
当thread_cache过长或者线程销毁则会将内存释放回central cache中的释放回来时 --use_count。当use_count减到0时则表示所有对象都回到了span则将span释放回page cachepage cache中会对前后相邻的空闲页进行合并。
//CentralCache.h//所有线程共享,整个进程仅有一个,所有弄成单例模上:饿汉(静态对象)
class CentralCache
{
public://获取单例对象static CentralCache* GetInstance(){return _sInst;}// 获取一个非空的spanSpan* GetOneSpan(Spanlist list, size_t size);// 从中心缓存获取一定数量的内存块对象给thread cachesize_t FetchRangeObj(void* start, void* end, size_t batchNum, size_t size);// 将一定数量的内存块对象释放到对应桶下的对应span对象中void ReleaseListToSpans(void* start, size_t size);private:CentralCache(){}CentralCache(const CentralCache) delete;CentralCache operator(const CentralCache) delete;
private:Spanlist _spanlists[NFREELIST];static CentralCache _sInst;
};考虑这样一个问题ThreadCache还回来的内存块还到CentralCache对应桶中但是对应桶可能有多个Span对象那这些还回来的内存块对象是属于那个Span呢
比如说目前有两个Span对象分别管理一页2000是Span1管理2001是Span2管理。下面有四个内存块还回来了假设1、2内存块是从Span1拿的3、4是从Span2拿的。那怎么确定对应内存块是从那个Span中拿的呢 以1、2内存块从Span1拿的为例既然是从Span1管理2000这一页拿这一页是划分为固定大小的小内存块你才拿的。那1、2这个内存块的地址是不是就在FA000和FA2000之间的拿它们的地址/8KB算出的整数还是2000。 因此我们可以用unordered_map或者map建立一个页号与Span一对一的映射关系。拿到内存块的地址/8KB 看它是那一页的然后根据页号就可以知道它是那个Span了。
这里我们使用unordered_map查找更快一些这个哈希桶等会PageCache也要用因此放在PageCache里。
//PageCache.h#includeCommon.h
//所有线程共享一个PageCache
class PageCache
{
public:static PageCache* GetInstance(){return _sInst;}//从PageCache第k号桶中获取一个span对象Span* NewSpan(size_t k);// 获取从对象到span的映射Span* MapObjectToSpan(void* obj);// 释放空闲span回到Pagecache并合并相邻的spanvoid ReleaseSpanToPageCache(Span* span);private:PageCache(){}PageCache(const PageCache) delete;PageCache operator(const PageCache) delete;static PageCache _sInst;
private:Spanlist _spanlists[NPAGES];std::unordered_mapPAGE_ID, Span* _idSpanMap;//记录页号和span一一对应关系
public:std::mutex _pageMtx; //整个锁
};然后在处理一下当CentralCache从PageCache拿到一个Span返回之前我们要把这个Span管理的页号与Span建立关系。
//从PageCache第k号桶中获取一个span对象
Span* PageCache::NewSpan(size_t k)
{assert(k 0 k NPAGES);//这里加锁递归会有死锁问题除非用递归互斥锁还有在外面调用这个函数之前加锁// 先检查第k个桶里面有没有spanif (!_spanlists[k].Empty()){Span* Kspan _spanlists[k].PopFront();//将Kspan给CentralCache之前,先将页号和span对应关系记录for (size_t i 0; i Kspan-_n; i){_idSpanMap[Kspan-_pageid i] Kspan;}//将管理k页的span给Central Cachereturn Kspan;}//走到这里第k个桶没有span那就继续往下面的桶找for (size_t i k 1; i NPAGES; i){if (!_spanlists[i].Empty()){//将一个管理更多页的span变成两个span一个管理k页一个管理i-k页Span* Ispan _spanlists[i].PopFront();//Span* Kspan new Span;Span* Kspan _spanPool.New();Kspan-_pageid Ispan-_pageid;Kspan-_n k;Ispan-_pageid k;Ispan-_n - k;//将Ispan挂在对应大小的桶_spanlists[Ispan-_n].PushFront(Ispan);//将Kspan给CentralCache之前,先将页号和span对应关系记录for (size_t i 0; i Kspan-_n; i){_idSpanMap[Kspan-_pageid i] Kspan;}//将管理k页的span给Central Cachereturn Kspan;}}//走到这里说明后面的位置都没有大页的span//那就去找堆申请一个128page的spanSpan* bigSpan new Span;void* ptr SystemAlloc(NPAGES - 1);bigSpan-_pageid (PAGE_ID)ptr PAGE_SHIFT;bigSpan-_n NPAGES - 1;//span挂在对应桶下_spanlists[bigSpan-_n].PushFront(bigSpan);//重复上述过程寻找k号桶的span一定还回一个spanreturn NewSpan(k);
}在增加一个地址转成页号找到对应Span的函数
// 获取从对象到span的映射
Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID id ((PAGE_ID)obj PAGE_SHIFT);auto ret _idSpanMap.find(id);if (ret ! _idSpanMap.end()){return ret-second;}else{assert(false);return nullptr;}
}接下来CentralCache就可以把一批内存块还给PageCache了。这里也需要记住在取PageCache之前先把对应桶锁解掉。不如别的线程来对应桶上申请释放就阻塞了。
// 将一定数量的内存块对象释放到对应桶下的对应span对象中
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{size_t index SizeClass::Index(size);_spanlists[index]._mtx.lock();//遍历将内存块对象还给对应的span//如何知道一个内存块对象是从那个span对象中拿走的?//用内存块中的地址/8kb,得到对应的页号,根据页号走到对应的span//如何根据页号找到对应span,用unordered_map记录页号和span一一对应关系//因为PageCache等会也要用到这个哈希桶,因为哈希桶定义到PageCache中while (start){void* next NextObj(start);Span* span PageCache::GetInstance()-MapObjectToSpan(start);//将start头插进对应span中NextObj(start) span-_freelist;span-_freelist start;span-_usecount--;//检查当前span中切分的内存块个数是否已经被还完了//如果还完,就将该span从CentralCache中删除,然后还给PageCache//PageCache看是否能将该span前后空闲span合并,减少内存碎片if (span-_usecount 0){//将该span从桶中删除_spanlists[index].Erase(span);span-_freelist nullptr;span-_prev nullptr;span-_next nullptr;//从CentarlCache去PageCache之前先把桶锁释放//如果不释放,万一其他线程来这个桶找span申请或者释放内存块,就阻塞住_spanlists[index]._mtx.unlock();//所有线程共享PageCache,所以先加锁PageCache::GetInstance()-_pageMtx.lock();PageCache::GetInstance()-ReleaseSpanToPageCache(span);PageCache::GetInstance()-_pageMtx.unlock();//回来再把对应桶锁加上_spanlists[index]._mtx.lock();}start next;}_spanlists[index]._mtx.unlock();
}1.3 page cache
释放内存
如果central cache释放回一个span则依次寻找span的前后page id的没有在使用的空闲span看是否可以合并如果合并继续向前寻找。这样就可以将切小的内存合并收缩成大的span减少内存碎片。
假设Span管理的页是起始页是2000一共管理5页和它相邻的前后Span如何找到
和它的前一个相邻的Span最后一页就是1999后一个相邻的Span的首先就是2005。我们还是可以通过找到页号就可以找到对应的Span。
合并相邻的页通过页号找到相邻的页看是否能合并如果该页的Span在PageCache说明该Span还没被使用可以合并。如果在CentralCache说明该Span正在被使用不能合并。
那如何确定一个Span是否在被使用呢能不能用Span中的_usecount0来判断
不能因为有时间间隔。如果线程thread1找PageCache拿到一个Span记住_usecount初始可是0然后切分成一块块小内存块在挂到CentralCache对应桶中然后正准备从这个Span拿一批内存块这个时候_usecount可还是0只有真正拿到了才会。另一个线程thread2在PageCache合并相邻的空闲的Span如果相邻的就是thread1正准备拿的Span你用_usecount0判断是否被使用那不就造成线程安全的问题了吗
所以不能用Span中的_usecount0判断一个Span是否在使用。我们再给Span加一个_isuse成员来判断Span是否在使用拿到CentralCache的Span就在使用在PageCache的Span就没有在使用。
//Span管理一个跨度的大块内存//管理以页为单位的大快内存
struct Span
{PAGE_ID _pageid 0;//管理一大块连续内存块的起始页的页号size_t _n 0;//管理几页Span* _prev nullptr;//带头双向循环链表前指针Span* _next nullptr;//带头双向循环链表后指针size_t _usecount 0;//记录Span对象中自由链表挂的内存块对象使用数量void* _freelist nullptr;//自由链表挂的是Span对象一块大连续内存块按照桶位置大小切分的一块块小的内存块bool _isuse false;//当前Span是否被使用
};当CentralPage从PageCache获取到一个新的Span就已经被用了增加一句代码。
// 获取一个非空的span
Span* CentralCache::GetOneSpan(Spanlist list, size_t size)
{//遍历对应桶中是否有span或者span中是否有内存对象Span* it list.Begin();while (it ! list.End()){if (it-_freelist){return it;}else{it it-_next;}}//当Central Cache对应桶下面没有span那就往下一层Page Cache找但是首先要先把对应桶锁释放//如果再来一个线程是申请内存但是没有内存锁住也没问题但是如果这个线程是来还内存的呢//锁住那不就还不了了因此往下走之前先把桶锁释放list._mtx.unlock();//走到这里说明该桶中并没有span或者span中没有内存对象,Central Cache那就找到下一层Page Cache要一个span//Page Cache 是一个按桶下标映射的哈希桶第i号桶表示这个桶下面的span管理的都是i页page//central找page要关注的是要的span是管理k页的span然后就去k号桶去要//PageCache的锁是一把整锁虽然也可以是桶锁但是消耗性能//当PageCache对应桶没有span就往下面桶继续找就涉及多个桶加锁解锁//因此我们在最外面直接把PageCache锁住只加锁解锁一次即可PageCache::GetInstance()-_pageMtx.lock();Span* span PageCache::GetInstance()-NewSpan(SizeClass::NumMovePage(size));//在CentralCache中的span是已经被使用的spanspan-_isuse true;//span被使用PageCache::GetInstance()-_pageMtx.unlock();//从PageCache拿上来的span是每一个线程独享的因此切片时不需要加锁//把从PageCache拿回的span切分成size大小的一个个内存块挂在自由链表最后再把span挂在对应的桶中//根据页号找到span管理的一大块内存的起始地址//地址用char* 方便下面地址char* start (char*)(span-_pageid PAGE_SHIFT);//计算这块内存有多大size_t bytes span-_n PAGE_SHIFT;//结尾地址char* end start bytes;//使用尾插法将大内存变成大小为size的一快快小内存挂在自由链表上span-_freelist start;start size;void* tail span-_freelist;while (start end){NextObj(tail) start;tail start;start size;}//自由链表最后为nullptrNextObj(tail) nullptr;//切好span以后需要把span挂到桶里面去的时候再加锁list._mtx.lock();//再把span挂在对应桶list.PushFront(span);return span;
}现在先别急着就去unordered_map去找在PageCache中相邻的Span你现在肯定也找不到还记得unordered_map目前记录的是什么吗它当前只记录了在CentralCache中的Span管理的页号与Span的对应关系也就是已经使用的Span的页号和Span的关系根本就没有记录在PageCache中Span管理的页号SPan对应的关系。因此还要在修改一下代码。
在PageCache的Span只需要记录它管理的首页和尾页与Span的映射关系就行了。合并我们就找相邻Span的首页和尾页就找到对应的Span了。对于在CentralCache中Span因为要被切分成一块块小内存然后才给ThreadCache用当小内存块回来的时候可能是这个Span中任意一页的小内块然后/8KB找到页号在找到对应的SPan。因此要把Span中每一页和Span对应关系都保存。
//从PageCache第k号桶中获取一个span对象
Span* PageCache::NewSpan(size_t k)
{assert(k 0 k NPAGES);//这里加锁递归会有死锁问题除非用递归互斥锁还有在外面调用这个函数之前加锁// 先检查第k个桶里面有没有spanif (!_spanlists[k].Empty()){Span* Kspan _spanlists[k].PopFront();//将Kspan给CentralCache之前,先将页号和span对应关系记录for (size_t i 0; i Kspan-_n; i){_idSpanMap[Kspan-_pageid i] Kspan;}//将管理k页的span给Central Cachereturn Kspan;}//走到这里第k个桶没有span那就继续往下面的桶找for (size_t i k 1; i NPAGES; i){if (!_spanlists[i].Empty()){//将一个管理更多页的span变成两个span一个管理k页一个管理i-k页Span* Ispan _spanlists[i].PopFront();//Span* Kspan new Span;Span* Kspan _spanPool.New();Kspan-_pageid Ispan-_pageid;Kspan-_n k;Ispan-_pageid k;Ispan-_n - k;//将Ispan挂在对应大小的桶_spanlists[Ispan-_n].PushFront(Ispan);//记录挂在PageCache中还未被使用的Ispan的页号和span对应关系//这里仅需记录这个span的首页和尾页与Ispan的对应关系即可,//不像返回给CentralCache的Kspan的需要把这个Kspan管理的每一页都和Kspan建立映射关系//因为合并仅需知道每个Ispan的首页和尾页就可以找到Ispan,而返回给CentralCache的Kspan,//首先需要将Kspan切成一块块小内存才行才能再给ThreadCache用,//当小内存回来/8kb可能是Kspan管理的其中某一页,才能知道该页对应span//_idSpanMap[Ispan-_pageid] Ispan;//_idSpanMap[Ispan-_pageid Ispan-_n - 1] Ispan;_idSpanMap.set(Ispan-_pageid, Ispan);_idSpanMap.set(Ispan-_pageid Ispan-_n - 1, Ispan);//将Kspan给CentralCache之前,先将页号和span对应关系记录for (size_t i 0; i Kspan-_n; i){_idSpanMap[Kspan-_pageid i] Kspan;}//将管理k页的span给Central Cachereturn Kspan;}}//走到这里说明后面的位置都没有大页的span//那就去找堆申请一个128page的spanSpan* bigSpan new Span;void* ptr SystemAlloc(NPAGES - 1);bigSpan-_pageid (PAGE_ID)ptr PAGE_SHIFT;bigSpan-_n NPAGES - 1;//span挂在对应桶下_spanlists[bigSpan-_n].PushFront(bigSpan);//重复上述过程寻找k号桶的span一定还回一个spanreturn NewSpan(k);
}准备工作做完了接下来就是CentralPage还回来一个Span看相邻的Span是否能合并解决内存碎片的问题。 // 释放空闲span回到Pagecache并合并相邻的span
void PageCache::ReleaseSpanToPageCache(Span* span)
{// 如何知道相邻的span是否能合并?// 通过自己的页号找到相邻的span看是否能合并,如果该页的span在PageCache说明该span还没有被使用,可以合并// 如果在CentralCache说明该span正在被使用,不能合并// 如何知道一个span是否被使用? 是用span中的usecount是否等于0吗? 不能!!// 这里有一个空档时间,当thread1线程通过TLS找到自己threadcache申请内存块,但是没有,// 就去找CentralCache,但是CentralCache对应桶下也没有,那就只能去找PageCache了// PageCache返回给CentralCache一个span,这个span的usecount初始可是0,// CentralCache拿到后对span这一大块内存切成一块块小内存// 在挂到对应桶下,但是这时候thread2,要合并这个span,那就有问题了,thread1正准备从这span拿一批量// 但是还没有拿到,这个span的usecount可还是0,只有拿走了usecount才会// thread2把这个span和自己span合并了,那就造成线程安全的问题!!// 因此需要给span对象加一个isuse成员记录这个span是否被使用// 如何通过页号找到相邻的页? 还是得用unordered_map记录页号合span对应关系// 但是目前的unordered_map只记录了给CentralCache已经被使用的span的页号和span对应关系// 并没有记录在PageCache的span的页号和span对应关系// 因此需要把在PageCache的span的页号和span对应关系也要记录在unordered_map中//先走前面相邻的span是否能合并,能合并就一直合while (1){PAGE_ID prevId span-_pageid - 1;auto ret _idSpanMap.find(prevId);// 前面的页号没有不合并了(堆申请内存已经到了起始地址)if (ret _idSpanMap.end()){break;}Span* prevSpan ret-second;// 前面相邻页的span在使用不合并了if (prevSpan-_isuse true){break;}// 合并出超过128页的span没办法挂在桶下不合并了if (prevSpan-_n span-_n NPAGES - 1){break;}// 用span合并prevSpanspan-_pageid prevSpan-_pageid;span-_n prevSpan-_n;// 将pevSpan从对应的桶中删除_spanlists[prevSpan-_n].Erase(prevSpan);delete prevSpan;// 这里可能有疑问,那遗留unordered_map中被合并的对应页和prevSpan之间一对一的关系难道不删除吗?// 因为prevSpan已经被删除了,在去通过已有页去找span那就是野指针了! 但其实并不用删除.// 首先被合并的页已经被span管理起来了,合并结束之后会被挂在对应桶下,并且记录该span首页和尾页与span的对应关系.// 当CentralCache要的时候,在把span切分成两个span,返回给CentralCache的Kspan每页都和Kspan重新进行映射// 留在PageCache的Ispan的首页和尾页也会和Ispan重新映射// 这样的话,以前被合并,遗留下来的页又和新得span建立了映射关系,就不会有通过页找span会有野指针的问题}//找后面相邻的span合并while (1){PAGE_ID nextId span-_pageid span-_n;auto ret _idSpanMap.find(nextId);// 后面的页号没有不合并了(堆申请内存已经到了结尾地址)if (ret _idSpanMap.end()){break;}Span* nextSpan ret-second;// 后面相邻页的span在使用不合并了if (nextSpan-_isuse true){break;}// 合并出超过128页的span没办法挂在桶下不合并了if (nextSpan-_n span-_n NPAGES - 1){break;}span-_n nextSpan-_n;_spanlists[nextSpan-_n].Erase(nextSpan);delete nextSpan;}//合并好的span挂在对应桶下_spanlists[span-_n].PushFront(span);span-_isuse false;//重新映射在PageCace的Span的首页和尾页与Span映射关系_idSpanMap[span-_pageid] span;_idSpanMap[span-_pageid span-_n - 1] span;
}关于释放内存就到这里了下面我们在对细节进行优化。
2. 申请和释放剩余补充
当要的内存小于256KB每个线程通过TLS找到自己的ThreadCache要如果没有就找CentralCache要在没有就找PageCache要在没有就找堆去要。
之前我们一直没谈的是大于256KB怎么办
其实这里分为分成两种情况256KB/8KB32PagePageCache最大的桶是128Page。因此就有 大于32Page 小于等于128Page找PageCache要。大于128Page直接找堆要。
不管是找PageCache要还是找堆要都是按页为单位对齐的。
//Common.hclass SizeClass
{//...static inline size_t _RoundUp(size_t bytes, size_t alignNum){return ((bytes alignNum - 1) ~(alignNum - 1));}static inline size_t RoundUp(size_t size){if (size 128){return _RoundUp(size, 8);}else if (size 1024){return _RoundUp(size, 16);}else if (size 8 * 1024){return _RoundUp(size, 128);}else if (size 64 * 1024){return _RoundUp(size, 1024);}else if (size 256 * 1024){return _RoundUp(size, 8 * 1024);}else//找PageCache或者堆按页为单位对齐{return _RoundUp(size, 1 PAGE_SHIFT);}return -1;}
};//ConcurrentAlloc.hstatic void* ConcurrentAlloc(size_t size)
{//大于256KB/8KB13page 小于等于128page 找PageCache要//大于128page找堆要if (size MAX_BYTES){//找PageCache或者堆要都是以页为单位对齐size_t alignnum SizeClass::RoundUp(size);//在PageCache那个桶size_t kpage alignnum PAGE_SHIFT;//在PageCache内部处理找是否找堆要PageCache::GetInstance()-_pageMtx.lock();Span* span PageCache::GetInstance()-NewSpan(kpage);//这里不需要记录这个span已经被使用,因为还得时候合并的这个span相邻页的span看是否在使用PageCache::GetInstance()-_pageMtx.unlock();//得到地址返回void* ptr (void*)(span-_pageid PAGE_SHIFT);return ptr;}else//小于256KB通过三层缓存要{//通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象if (pTLSThreadCache nullptr){pTLSThreadCache new ThreadCache;}return pTLSThreadCache-Allocate(size);}
}这里在把找PageCache要一个Span处理一下
//从PageCache第k号桶中获取一个span对象
Span* PageCache::NewSpan(size_t k)
{assert(k 0);//大于 128page找堆要if (k NPAGES - 1){void* ptr SystemAlloc(k);//申请一个span对象管理这块内存,释放内存的时候要对应的spanSpan* span new Span;span-_pageid (PAGE_ID)ptr PAGE_SHIFT;span-_n k;_idSpanMap[span-_pageid] span;return span;}//这里加锁递归会有死锁问题除非用递归互斥锁还有在外面调用这个函数之前加锁// 先检查第k个桶里面有没有spanif (!_spanlists[k].Empty()){Span* Kspan _spanlists[k].PopFront();//将Kspan给CentralCache之前,先将页号和span对应关系记录for (size_t i 0; i Kspan-_n; i){_idSpanMap[Kspan-_pageid i] Kspan;}//将管理k页的span给Central Cachereturn Kspan;}//走到这里第k个桶没有span那就继续往下面的桶找for (size_t i k 1; i NPAGES; i){if (!_spanlists[i].Empty()){//将一个管理更多页的span变成两个span一个管理k页一个管理i-k页Span* Ispan _spanlists[i].PopFront();Span* Kspan new Span;Kspan-_pageid Ispan-_pageid;Kspan-_n k;Ispan-_pageid k;Ispan-_n - k;//将Ispan挂在对应大小的桶_spanlists[Ispan-_n].PushFront(Ispan);//记录挂在PageCache中还未被使用的Ispan的页号和span对应关系//这里仅需记录这个span的首页和尾页与Ispan的对应关系即可,//不像返回给CentralCache的Kspan的需要把这个Kspan管理的每一页都和Kspan建立映射关系//因为合并仅需知道每个Ispan的首页和尾页就可以找到Ispan,而返回给CentralCache的Kspan,//首先需要将Kspan切成一块块小内存才行才能再给ThreadCache用,//当小内存回来/8kb可能是Kspan管理的其中某一页,才能知道该页对应span_idSpanMap[Ispan-_pageid] Ispan;_idSpanMap[Ispan-_pageid Ispan-_n - 1] Ispan;//将Kspan给CentralCache之前,先将页号和span对应关系记录for (size_t i 0; i Kspan-_n; i){_idSpanMap[Kspan-_pageid i] Kspan;}//将管理k页的span给Central Cachereturn Kspan;}}//走到这里说明后面的位置都没有大页的span//那就去找堆申请一个128page的spanSpan* bigSpan new Span;void* ptr SystemAlloc(NPAGES - 1);bigSpan-_pageid (PAGE_ID)ptr PAGE_SHIFT;bigSpan-_n NPAGES - 1;//span挂在对应桶下_spanlists[bigSpan-_n].PushFront(bigSpan);//重复上述过程寻找k号桶的span一定还回一个spanreturn NewSpan(k);
}释放也是一样小于256KB的通过TLS找自己的ThreadCache释放大于32Page小于等于128Page还给PageCache大于128Page还直接返给堆。
static void ConcurrentFree(void* ptr,size_t size)
{//大于32page小于等于128page直接还给PageCache,//大于128page直接还给堆if (size MAX_BYTES){//根据地址找转换为对应的页号,通过页号找到对应的span,在找到对应内存大小Span* span PageCache::GetInstance()-MapObjectToSpan(ptr);PageCache::GetInstance()-_pageMtx.lock();PageCache::GetInstance()-ReleaseSpanToPageCache(span);PageCache::GetInstance()-_pageMtx.unlock();}else{assert(pTLSThreadCache);pTLSThreadCache-Deallocate(ptr, size);}
}