当前位置: 首页 > wzjs >正文

青海企业网站开发定制郑州网站开发公司

青海企业网站开发定制,郑州网站开发公司,自己开店,深圳高端网站制作费用前言:经过前3期的攻坚,我们已完整实现了内存动态申请的核心模块。接下来将进入关键阶段——内存释放机制的理解与实现,这是构建完整 高并发内存池 的最后一块技术拼图。该模块完成后,项目主体架构将基本成型(达90%&…

        前言:经过前3期的攻坚,我们已完整实现了内存动态申请的核心模块。接下来将进入关键阶段——内存释放机制的理解与实现,这是构建完整 高并发内存池 的最后一块技术拼图。该模块完成后,项目主体架构将基本成型(达90%),后续主要聚焦于边界测试和性能调优。欢迎关注本专栏的开发者们持续追踪代码演进,共同探讨高并发内存池的工程实践优化方案。

项目专栏:高并发内存池_敲上瘾的博客-CSDN博客

个人主页:敲上瘾-CSDN博客

目录

​编辑

一、ThreadCache内存释放

二、CentralCache内存回收

三、PageCache内存回收

四、源码


一、ThreadCache内存释放

        在ThreadCache层我们探讨过这样一个问题,某一时段某个线程可能需要大量内存,不断向span桶中申请。而用完后这些内存就被放回它自己的自由链表桶了,导致后续线程再需要向span桶申请内存时资源不足,大部分内存都堆积在这个线程自由链表桶

        为了解决这样的问题,我们需要将部分内存归还给CentralCache。那么,自由链表达到多长时需要放回span桶呢?

        之前在设计应该从span桶取多少内存块比较合适时我们也有过类似的困扰,当时设计了一个慢增长算法,每个自由链表中存了一个_maxSize,这里我们就做简单一点,用_maxSize来判断,即当一个自由链表长度超过一个批量(_maxSize)应该申请的内存时,我们把自由链表的_maxSize个节点还回span桶

首先在class ThreadCache中声明一个函数,用来把要释放的内存截下来送入CentralCache层。

        作用是把list中自由链表的_maxSize个节点截下来,然后送入CentralCache层,因为需要找到对应的哈希桶,所以传入内存块大小。

然后在Deallocate函数(释放内存)中检查是否满足归还span桶的条件,如下:

void ThreadCache::Deallocate(void* obj, size_t bytes)
{int index = SizeClass::Index(bytes);_freelists[index].Push(obj);if (_freelists[index].Size() >= _freelists[index].MaxSize()){size_t size = SizeClass::RoundUp(bytes);ListTooLong(_freelists[index], size);}
}

注:Size用来获取链表长度,这里就不展示了,可以用这两种方法之一:

  • 直接遍历链表并计数。
  • 添加并维护_size成员函数,Size()中直接返回_size。 

因为每次使用Size函数都遍历链表会影响效率,所以这里选用添加_size成员的方法。

接下来实现ListTooLong函数:

void ThreadCache::ListTooLong(FreeList& list, size_t size)
{void* start = nullptr;void* end = nullptr;list.PopRange(start, end, list.MaxSize());//交给CentralCacheCentralCache::GetInstance()->ReleaseListToSpans(start, size);
}

        同样的使用start和end指针去取内存,在class FreeList里封装一个函数PopRange,如果你看懂了之前内存申请的代码,这里的实现就显得很简单,不在一一讲解。代码示例:

//取一段内存
void PopRange(void* start, void* end, int num)
{assert(num <= _size);start = end = _list_head;for (int i = 0; i < num - 1; i++){end = Nextobj(end);}_list_head = Nextobj(end);Nextobj(end) = nullptr;_size -= num;
}

二、CentralCache内存回收

小节目标:ReleaseListToSpans(start, size)的实现

        CentralCache内存回收的主要任务就是把ThreadCache送来的内存连接到span桶中,其次,同样的问题,什么时候送到下一层呢?这个问题我们探讨过,就是span中的所有内存块都回收回来时

        记住:理解为什么要span中的所有内存块回收回来后送入下一层这一点很重要,在下文会讲到。

回收的内存插入到Span的自由链表中

        现在有一个问题:内存块要插入到哪个span?我们知道的size仅能找到它所在的哈希桶,也就是一个span链,要具体到某个span节点是一个很让人头疼的问题。

注意:不能粗鲁地把内存块连接到一个随便找的span,这样的话,属于不同页的内存块就混乱在一起,后面的PageCache就没法玩了!

        追溯到本质(思考内存块从“出生”到现在的这个过程):内存块连接到某个span,实际上是要找到内存块属于那个页号,然后连接到这个页号对应的span中。

        页号成为了它们之间的脐带,内存块地址除以8KB就是它所在的页,而一个span的信息中也能找到它管理着哪些页,只需要做一个页号到span的映射(即哈希表)就能解决问题。页级别的内存管理是在PageCache,我们把哈希映射在PageCache层创建和维护。

在PageCache中添加成员:

  • unordered_map<PAGE_ID, Span*> _idSpanMap;  

        _idSpanMap在哪里填写呢?答:在页分配时。在向系统申请内存和给span分配页前是不能确定页号和span的对应关系的,所以哈希映射是随程序运行的动态填写过程。

        将要被连接到CentralCache层的span,需要把它管理的所有页都映射到span中,因为拆开的内存块能获取到的是一个确定的页号。放回PageCache的span只需要它管理的开始页号和尾页号映射到span,用于span合并时对相邻页的查找。

如下:

Span* PageCache::NewSpan(size_t k)
{assert(k > 0 && k < NPAGES);if (!_spanLists[k].Empty()){return _spanLists[k].PopFront();}for (int i = k + 1; i < NPAGES; i++){//切割页(即页分配)if (!_spanLists[i].Empty()){Span* nSpan = _spanLists[i].PopFront();Span* kSpan = new Span;kSpan->_n = k;//页数nSpan->_n -= k;//填写页号kSpan->_pageId = nSpan->_pageId;nSpan->_pageId += k;//建立映射_idSpanMap[nSpan->_pageId] = nSpan;_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;for (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageId + i] = kSpan;}_spanLists[nSpan->_n].PushFront(nSpan);kSpan->_isUse = true;//表示span被CentralCache使用,在下文会讲到。return kSpan;}}//向系统申请内存Span* span = new Span;void* ptr = SystemAlloc(NPAGES - 1);span->_pageId = (PAGE_ID)ptr>>PAGE_SHIFT;span->_n = NPAGES - 1;_spanLists[span->_n].PushFront(span);return NewSpan(k);
}

对于使用内存块查找span我们封装一个函数,如:

Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID index = (PAGE_ID)obj >> PAGE_SHIFT;auto ret = _idSpanMap.find(index);if (ret == _idSpanMap.end()){assert(false);return nullptr;}else{return ret->second;}
}

接下来我们来实现ReleaseListToSpans(start, size),它的核心工作:

  1. 找到每个内存块对应的span。
  2. 把内存块连接到span的自由链表中。
  3. 判断内存块是否全部回收,如果是,就将span送入PageCache中。

注意:找到哈希桶后需要加桶锁。

        对于第3点,在把span送入PageCache之前,需要把span从CentralCache移除,并把span前后指针指向空,自由链表指向空。其次走到这一步意味着程序将离开CentralCache层进入PageCache层,在此之前把桶锁解了,方便让其他线程使用,然后加锁执行PageCache的内存回收函数,解锁,然后再次申请桶锁

代码示例:

void CentralCache::ReleaseListToSpans(void* start, size_t size)
{void* p  = start;//找到每个p所在Span并连接到自由链表//加桶锁int index = SizeClass::Index(size);_spanLists[index]._mtx.lock();while (p){void* next = Nextobj(p);Span* span = PageCache::GetInstance()->MapObjectToSpan(p);Nextobj(p) = span->_freeList;span->_freeList = p;span->_useCount--;if (span->_useCount == 0){//移出Span链表_spanLists->Erase(span);span->_freeList = nullptr;span->_next = span->_prev = nullptr;//交给PageCache回收_spanLists[index]._mtx.unlock();PageCache::GetInstance()->_pageMtx.lock();PageCache::GetInstance()->ReleaseSpanToPageCache(span);PageCache::GetInstance()->_pageMtx.unlock();_spanLists[index]._mtx.lock();}p = next;}_spanLists[index]._mtx.unlock();
}

内存块全部回收后送入PageCache的作用:

        内存块回收到span后,它在自由链表中的排布已经不像初次从PageCache取出那样是有序的,而是混乱的。等span全部内存块回收后,就可以把它们看做一个整体,尽管内存块的排布是混乱的也没关系,这只是逻辑上的混乱,回到PageCache层后它就是一个大块页。等PageCache再次分页时,内存块次序恢复和虚拟地址一样,增加高速缓存命中率。

三、PageCache内存回收

小节目标:ReleaseSpanToPageCache(span)的实现。

        当PageCache拿到span后不要直接连接到哈希桶里,而把它前后能合并的页都合并在一起组成一个大块的页,然后再连接到哈希桶,这样可以缓解内存外碎片

        注意 是把它前后的页span合并在一起,只有连续页的span才能合并,而不是前后桶的span,前后页的span也无法依据桶来找,要找到管理它前后页的span需要用哈希映射。

        但又会带来一些问题,哈希映射得到的span可能是在CentralCache中,也有可能是在PageCache中,因为我们用的是同一个哈希映射,而合并span,指的是PageCache中的span,要如何解决?

        这里我们选择在span中添加成员变量_isUse来判断span是否分配给了CentralCache,这样就可以把它们区分开。先暂时这样写,后期再做优化。比如一个span的_isUse初始状态为false,分配给CentralCache后设为true,PageCache的回收处理后设为false。

合并条件:

  • 它相邻的页在哈希映射中。
  • 该页不被_CentralCache使用。
  • 两个span的页数相加不超过128。

合并过程:

  • 用来储存合并后结果的span:页号更新为两个span中较小的页号,页数更新为两者的页数和。
  • 另一个span:从PageCache的哈希桶中移除,delete删除,释放空间。

合并结果处理:

  • 按上面逻辑把前后页合并得到的span,_isUse设为false,将其连接到PageCache的哈希桶,然后做页到span的哈希映射。

代码示例:

void PageCache::ReleaseSpanToPageCache(Span* span)
{PAGE_ID n = span->_pageId;//向前合并while (true){PAGE_ID prev = span->_pageId - 1;auto ret = _idSpanMap.find(prev);if (ret != _idSpanMap.end()&&!ret->second->_isUse){if (span->_n + ret->second->_n >= NPAGES)break;span->_pageId = ret->second->_pageId;span->_n += ret->second->_n;_spanLists->Erase(ret->second);delete ret->second;}else break;}//向后合并while (true){PAGE_ID prev = span->_pageId + 1;auto ret = _idSpanMap.find(prev);if (ret != _idSpanMap.end() && !ret->second->_isUse){if (span->_n + ret->second->_n >= NPAGES)break;span->_n += ret->second->_n;_spanLists->Erase(ret->second);delete ret->second;}else break;}span->_isUse = false;_spanLists->PushFront(span);_idSpanMap[span->_pageId] = span;_idSpanMap[span->_pageId + span->_n - 1] = span;
}

四、源码

需要源码的小伙伴到我的gitee上取:

内存释放/PageCache · 敲上瘾/ConcurrentMemoryPool - 码云 - 开源中国

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!74c0781738354c71be3d62e05688fecc.png

http://www.dtcms.com/wzjs/497212.html

相关文章:

  • 郑州网站建设排行榜网络营销策划
  • 网站建设内容论文代刷网站推广
  • 网站应用软件设计网络营销促销策略有哪些
  • 沈阳建站多少钱成品ppt网站国外
  • 网站优化公司开始上班了如何提高自己在百度的排名
  • 游戏网站建设方案自己怎么制作一个网站
  • 网站的重要目录对百度进行了封禁最好的网站设计公司
  • 崇州网站建设微信软文范例
  • 免费flash网站模板百度商店应用市场
  • 桂林论坛网站有哪些成都百度推广电话
  • 做美女网站挣钱百度百科怎么创建自己
  • 家居网站建设基本流程精准引流怎么推广
  • 建设宠物食品网站的功能定位今日国内新闻热点
  • 爱网站排行东莞网站建设公司排名
  • 动漫设计学校北京seo优化分析
  • 广州网站设计报价做任务赚佣金的平台
  • 深圳的网站建设公司 湖南岚鸿适合成人参加的培训班
  • 用钩针做花网站微信友情链接网址
  • 广东圆心科技网站开发建站教程详解网络推广方案的内容
  • 赤峰建设局网站软文之家
  • 欧美真人做爰视频网站b2b电子商务网站
  • 响应式网站开发教程推广普通话
  • 珠海做网站推广公司上海企业网站推广
  • 珠海自适应网站建设如何联系百度平台客服
  • 学慧网的网站是谁家做的苏州网络推广服务
  • 用dw做红米网站常州seo关键词排名
  • 做分析图的网站360营销推广
  • 基层民警精神文化建设网站百度推广排名怎么做的
  • 网站新功能演示用什么技术做的适合seo软件
  • 宁德网站建设维护环球军事网