山东网站建设设计小清新个人网站
news/
2025/9/27 16:53:40/
文章来源:
山东网站建设设计,小清新个人网站,如何撰写网站建设方案,网络推广公司招聘七、高并发内存池–Page Cache
7.1 PageCache的工作原理
PageCache是以span的大小(以页为单位)和下标一一对应为映射关系的哈希桶#xff0c;下标是几就说明这个哈希桶下挂的span的大小就是几页的#xff0c;是绝对映射的关系。因为PageCache也是全局只有唯一一个的#x…七、高并发内存池–Page Cache
7.1 PageCache的工作原理
PageCache是以span的大小(以页为单位)和下标一一对应为映射关系的哈希桶下标是几就说明这个哈希桶下挂的span的大小就是几页的是绝对映射的关系。因为PageCache也是全局只有唯一一个的所以为了防止出现线程安全的问题在访问PageCache之前要先加上一把大锁锁住整个PageCache为什么这里是加一把大锁锁住整个PageCache而不是加桶锁锁住要访问的那个桶呢
因为当Central Cache向PageCache获取一个K页的span的时候在PageCache中下标为K的哈希桶中不一定有span此时PageCache并不是直接向系统堆中申请一块K页的span内存因为如果这样处理的话会出现频繁地向系统申请内存的情况并且每次向堆申请的内存都只是K页大小的如果每一次的K都是1或者2这样会使堆申请出来的内存都是很碎片化的不便于堆做内存管理所以PageCache的处理策略是如果当前下标的哈希桶中没有span对象那么就从当前位置开始往后遍历如果后面的哈希桶中有span对象就把这个span切成一个K页的kspan和一个(n-k)页的span把kspan切成一个一个的小对象连接到kspan中的自由链表并把这个切好的kspan返回给CentralCache对应位置的哈希桶剩下的(n-k)页大小的span就挂到PageCache中n-k位置的哈希桶。
如果把PageCache中剩下的所有的哈希桶都遍历完都没有找到一个span那么就向系统申请一个128(这个128是自定义的)页大小的大span重复上面的切分操作最后返回一个切好的K页的span给CentralCache对应位置的哈希桶中再让CentralCache分若干个小对象给ThreadCacheThreadCache再返回一个给上层。
综上也就不难发现PageCache不能用桶锁而是要用一把大锁因为在CentralCache向PageCache中获取一个span时不仅仅是会访问PageCache中的某一个位置的哈希桶而是有可能往后遍历其它的哈希桶并访问所以加桶锁只是锁住了一个桶并不能锁住后续遍历的其它位置的哈希桶所以这里要加一把大锁。其实从技术上来说用桶锁也是可以的只不过在遍历PageCache后面的每一个哈希桶前都要对先对这个桶加上桶锁访问完之后还要解掉桶锁如此一来必然会出现大量的上锁解锁的操作反而会降低了内存池的效率所以加一把大锁就行了。 7.2 PageCache.h
//页缓存CentralCache需要向PageCache申请K页大小的spanPageCache向系统申请128页大小的span
//因为PageCache也是全局只有唯一一个的所以也把PageCache设计成单例模式
class PageCache
{
public:static PageCache* GetInstance(){return _sInst;}//提供给CentralCache向PageCache获取一个k页的span时使用Span* NewSpan(size_t k);//根据obj的地址转换成页号进而通过哈希表找到页号对应的SpanSpan* MapObjectToSpan(void* obj);//CentralCache把span还回来给PageCachevoid ReleaseSpanToPageCache(Span* span);//PageCache是整个进程唯一的所以所有的线程都有可能//访问PageCache存在线程安全问题所以需要加锁这里是加一把大锁而不是桶锁std::mutex _pageMtx;private://单例模式需要把构造函数私有化防止别人创建对象PageCache(){}PageCache(const PageCache) delete;private:SpanList _spanLists[NPAGES];//有多少也大小的span数组就有多大下标和span的大小一一对应//声明静态对象static PageCache _sInst;//如果PageCache中所有的哈希桶都没有span需要从系统堆中申请内存时//要脱离malloc所以直接用我们写好的定长内存池申请内存即可定长内存//池中封装了系统调用接口直接向堆申请内存和释放内存ObjectPoolSpan _spanPool;//保存页号和span的映射关系为了后续能找到每一个还回来的小对象属于哪一个spanstd::unordered_mapPAGE_ID, Span* _idSpanMap;};
7.3 PageCache.cpp //静态对象需要在类内声明类外面定义
PageCache PageCache::_sInst;//k代表的是这个span的大小k页
Span* PageCache::NewSpan(size_t k)
{assert(k 0);//如果申请的span的大小大于128页则需要直接向堆申请//因为PageCache只有128个有效的哈希桶if (k NPAGES - 1){//向堆申请k页内存void* ptr SystemAlloc(k);Span* kSpan _spanPool.New();//_spanPoll是定长内存池对象直接向堆申请内存//地址转化成页号kSpan-_pageId (PAGE_ID)ptr PAGE_SHIFT;kSpan-_n k;//把页号和Kspan的映射关系放进Map中_idSpanMap[kSpan-_pageId] kSpan;return kSpan;}else{//如果PageCache第k个位置的哈希桶上有k页大小的span则直接返回一个spanif (!_spanLists[k].Empty()){Span* kSpan _spanLists[k].PopFront();//把kSpan的页号和对应的Span*的映射关系存放到哈希桶中去,方便//CentralCache回收小块内存时查找对应的span//kSpan代表的是一个k页大小的Span的大块内存kSpan-_pageId//代表这个大块内存的起始地址有k页所以这k页映射到的都是这个Span// //这里曾经没有存映射关系导致归还小内存块的时候找不到所属的span细节for (PAGE_ID i 0; i kSpan-_n; i){_idSpanMap[kSpan-_pageId i] kSpan;}return kSpan;}//走到这里说明PageCache第k个位置的哈希桶没有k页大小的span则需要遍历//后面的大于k页的哈希桶找到了一个n页大小的span就把这个span切分成一个// k页大小的span和一个n-k页大小的spank页的返回n-k页的挂到对应的哈希桶中//遍历后面的哈希桶for (size_t i k 1; i NPAGES; i){//找到了一个不为空的i页的哈希桶就对它进行切分if (!_spanLists[i].Empty()){//k页的spanSpan* kSpan _spanPool.New();//n页的spanSpan* nSpan _spanLists[i].PopFront();//把这个i页大小的span拿出来进行切分//开始把一个n页的span切分成一个k页的span和一个n-k页的span// //从nSpan的头上切k页给kSpan所以kSpan的页号就是nSpan的页号kSpan-_pageId nSpan-_pageId;kSpan-_n k;//kSpan的页数是k//被切分以后nSpan的页号需要k页因为头nSpan的头k页已经切分给了kSpannSpan-_pageId k;nSpan-_n - k;//nSpan的页数要-k页因为nSpan被切走了k页//把kSpan的页号和对应的Span*的映射关系存放到哈希桶中去,方便// CentralCache回收小块内存时查找对应的span//kSpan代表的是一个k页大小的Span的大块内存kSpan-_pageId//代表这个大块内存的起始地址有k页所以这k页映射到的都是这个Spanfor (PAGE_ID i 0; i kSpan-_n; i){_idSpanMap[kSpan-_pageId i] kSpan;}//nSpan被切分后的首页和尾页的页号和nspan的映射关系也需要保存起来//以便后续合并因为合并的方式是前后页合并往前找肯定找到的是一个span的//最后一页往后找一定找的是一个span的第一页所以挂在PageCache对应哈希桶//的span的第一页和最后一页与span的关系也需要保存起来_idSpanMap[nSpan-_pageId] nSpan;_idSpanMap[nSpan-_pageId nSpan-_n - 1] nSpan;//把剩余的n-k页的span头插到对应下标的哈希桶中nSpan-_n已经是改过的了不用再-k_spanLists[nSpan-_n].PushFront(nSpan);return kSpan;}}//走到这里说明前面的NPAGES个哈希桶中都没有Span(例如第一次申请内存时)//则需要向堆申请一个128页大小的span大块内存挂到对应的哈希桶中void* ptr SystemAlloc(NPAGES - 1);Span* bigSpan _spanPool.New();bigSpan-_pageId (PAGE_ID)ptr PAGE_SHIFT;//内存的地址需要转换成页号映射到对应的哈希桶中bigSpan-_n NPAGES - 1;//大块内存的页数描述这个bigspan的大小//把NPAGES-1页大小的span头插到对应NPAGES-1号桶中去_spanLists[bigSpan-_n].PushFront(bigSpan);//本质是运用了复用的设计避免代码中出现重复的逻辑return NewSpan(k);}}Span* PageCache::MapObjectToSpan(void* obj)
{//计算出obj对应的页号PAGE_ID id (PAGE_ID)obj PAGE_SHIFT;//访问_idSpanMap的时候需要加锁避免线程安全的问题//这里使用C11的RAII锁出了这个函数这把锁会自动解掉std::unique_lockstd::mutex lock(_pageMtx);//通过页号查找该内存块对应的是哪一个spanauto ret _idSpanMap.find(id);if (ret ! _idSpanMap.end()){return ret-second;}else{assert(false);return nullptr;}
}//CentralCache把span还回来给PageCache
void PageCache::ReleaseSpanToPageCache(Span* span)
{//如果span的页数大于128页则说明这个span是从堆上直接申请的//直接释放给堆即可不能挂到PageCache的哈希桶中因为PageCache一个只有128个桶if (span-_n NPAGES - 1){void* ptr (void*)(span-_pageId PAGE_SHIFT);SystemFree(ptr);_spanPool.Delete(span);return;}while (1){//找前一页的span看是否能够和当前页合并如果能则循环向前合并直到不能合并为止PAGE_ID prevId span-_pageId - 1;auto ret _idSpanMap.find(prevId);//_idSpanMap中没找到前一页和对应span说明前一页的内存没有被申请结束合并if (ret _idSpanMap.end()){break;}Span* prevSpan ret-second;//如果前一页对应的span在CentralCache中正在被使用结束合并if (prevSpan-_isUse true){break;}//如果和前一页合并之后会超过哈希桶的最大的映射返回结束合并if (prevSpan-_n span-_n NPAGES - 1){break;}//合并span和prevSpanspan-_pageId prevSpan-_pageId;span-_n prevSpan-_n span-_n;//合并之后需要把prevSpan在对应的哈希桶中删除掉_spanLists[prevSpan-_n].Erase(prevSpan);//因为prevSpan已经被合并到了span中所以prevSpan对应的内存可以delete掉了_spanPool.Delete(prevSpan);}while (1){//找span的下一个span的起始页号PAGE_ID nextId span-_pageId span-_n;auto ret _idSpanMap.find(nextId);if (ret _idSpanMap.end()){break;}Span* nextSpan ret-second;if (nextSpan-_isUse true){break;}if (nextSpan-_n span-_n NPAGES - 1)//曾经写成NPAGES1了{break;}//span的起始页号不变页数相加span-_n span-_n nextSpan-_n;//合并之后需要把prevSpan在对应的哈希桶中删除掉_spanLists[nextSpan-_n].Erase(nextSpan);_spanPool.Delete(nextSpan);}//合并得到的新的span需要挂到对应页数的哈希桶中_spanLists[span-_n].PushFront(span);//在PageCache中的span要设置为false好让后面相邻的span来合并span-_isUse false;//为了方便后续的合并需要把span的起始页号和尾页号和span建立映射关系_idSpanMap[span-_pageId] span;_idSpanMap[span-_pageId span-_n - 1] span;}
以上就是高并发内存池三层模型的核心部分后面的文章是对该高并发内存池的性能测试以及针对性能的瓶颈区做出相应的优化策略。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/919691.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!