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

【高性能内存池】page cache内存回收过程 8

当central cache中的_useCount减到0了,说明thread cache将申请的内存都还给central cache了,这时要将central cache中的span还给page cache。

内存还给page cache之后不是单纯的挂载到桶下面就行了,为了解决内存碎片的问题,还需要进行内存合并。

1.page cache前后页的合并的规则是什么?

当page cache拿到还回来的span之后,可以尝试向前或者向后合并。
如果拿到的span的页号是num,span的大小是n页,
向前合并:判断第num-1页是否是空闲的,如果是就合并。一直往前试探,直到找到不空闲的span,直到不能合并为止。
向后合并:判断第num + n页是否是空闲的,如果是就合并。一直往后试探,直到找到不空闲的span,直到不能合并为止。

2.如何判断span是否空闲?

通俗的说,一个span如果挂在page cache中就代表是空闲的,如果被拿给central cache就是不空闲的。
在这里插入图片描述

从上图可以看到,尽管地址是连续的,但是有些span是空闲的,有些span是不空闲的。
而判断是否空闲的方法就是观察span是否在page cache中。
因此,可以定义一个变量_isuse,当central cache从page cache中申请span时,_isues = true;当central cache将span还给page cache时,_isuse = false

因此Span的结构如下:

struct Span
{PAGE_ID _pageId = 0; // 大块内存起始页的页号size_t  _n = 0;      // 页的数量Span* _next = nullptr;	// 双向链表的结构Span* _prev = nullptr;size_t _objSize = 0;  // 切好的小对象的大小size_t _useCount = 0; // 切好小块内存,被分配给thread cache的计数void* _freeList = nullptr;  // 切好的小块内存的自由链表bool _isUse = false;          // 是否在被使用
};

3. page cache中span如何映射

由于在合并page cache当中的span时,需要通过页号找到其对应的span,而一个span是在被分配给central cache时,才建立的各个页号与span之间的映射关系,因此page cache当中的span也需要建立页号与span之间的映射关系。

与central cache中的span不同的是,在page cache中,只需建立一个span的首尾页号与该span之间的映射关系。因为当一个span在尝试进行合并时,如果是往前合并,那么只需要通过一个span的尾页找到这个span,如果是向后合并,那么只需要通过一个span的首页找到这个span。也就是说,在进行合并时我们只需要用到span与其首尾页之间的映射关系就够了。

因此当我们申请k页的span时,如果是将n页的span切成了一个k页的span和一个n-k页的span,我们除了需要建立k页span中每个页与该span之间的映射关系之外,还需要建立剩下的n-k页的span与其首尾页之间的映射关系。

//获取一个k页的span
Span* PageCache::NewSpan(size_t k)
{assert(k > 0 && k < NPAGES);//先检查第k个桶里面有没有spanif (!_spanLists[k].Empty()){Span* kSpan = _spanLists[k].PopFront();//建立页号与span的映射,方便central cache回收小块内存时查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageId + i] = kSpan;}return kSpan;}//检查一下后面的桶里面有没有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页下来kSpan->_pageId = nSpan->_pageId;kSpan->_n = k;nSpan->_pageId += k;nSpan->_n -= k;//将剩下的挂到对应映射的位置_spanLists[nSpan->_n].PushFront(nSpan);//存储nSpan的首尾页号与nSpan之间的映射,方便page cache合并span时进行前后页的查找_idSpanMap[nSpan->_pageId] = nSpan;_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;//建立页号与span的映射,方便central cache回收小块内存时查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; i++){_idSpanMap[kSpan->_pageId + i] = kSpan;}return kSpan;}}//走到这里说明后面没有大页的span了,这时就向堆申请一个128页的spanSpan* 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);
}

4. page cache合并过程是怎样的?

不能合并的情况:

  1. 前面的页号还未向系统申请(这里前面的页号表示前面的页号所代表的span,简称)
  2. 前面的页号正在被使用
  3. 合并完之后的页号超过128页,需要还给堆
//释放空闲的span回到PageCache,并合并相邻的span
void PageCache::ReleaseSpanToPageCache(Span* span)
{//对span的前后页,尝试进行合并,缓解内存碎片问题//1、向前合并while (1){PAGE_ID prevId = span->_pageId - 1;auto ret = _idSpanMap.find(prevId);//前面的页号没有(还未向系统申请),停止向前合并if (ret == _idSpanMap.end()){break;}//前面的页号对应的span正在被使用,停止向前合并Span* prevSpan = ret->second;if (prevSpan->_isUse == true){break;}//合并出超过128页的span无法进行管理,停止向前合并if (prevSpan->_n + span->_n > NPAGES - 1){break;}//进行向前合并span->_pageId = prevSpan->_pageId;span->_n += prevSpan->_n;//将prevSpan从对应的双链表中移除_spanLists[prevSpan->_n].Erase(prevSpan);delete prevSpan;}//2、向后合并while (1){PAGE_ID nextId = span->_pageId + span->_n;auto ret = _idSpanMap.find(nextId);//后面的页号没有(还未向系统申请),停止向后合并if (ret == _idSpanMap.end()){break;}//后面的页号对应的span正在被使用,停止向后合并Span* nextSpan = ret->second;if (nextSpan->_isUse == true){break;}//合并出超过128页的span无法进行管理,停止向后合并if (nextSpan->_n + span->_n > NPAGES - 1){break;}//进行向后合并span->_n += nextSpan->_n;//将nextSpan从对应的双链表中移除_spanLists[nextSpan->_n].Erase(nextSpan);delete nextSpan;}//将合并后的span挂到对应的双链表当中_spanLists[span->_n].PushFront(span);//建立该span与其首尾页的映射_idSpanMap[span->_pageId] = span;_idSpanMap[span->_pageId + span->_n - 1] = span;//将该span设置为未被使用的状态span->_isUse = false;
}

在这里插入图片描述


http://www.mrgr.cn/news/40468.html

相关文章:

  • 构建高效的足球青训后台:Spring Boot应用
  • 生信初学者教程(十九):免疫浸润细胞
  • 使用rust写一个Web服务器——单线程版本
  • Python机器学习:数据预处理与清洗的打开方式
  • 【机器学习-无监督学习】降维与主成分分析
  • 用示波器测动态滞回线
  • 数据中心交换机与普通交换机之间的区别到底在哪里?
  • c++11新特性-下
  • PCL 将点云投影到圆柱(Ransac拟合)
  • 基于NFSR和S盒的国产流密码算法Bagua
  • 快速了解:MySQL InnoDB和MyISAM的区别
  • MATLAB数字水印系统
  • 计算机网络自顶向下(2)----socket编程
  • 【YOLO系列】YOLOv11正式发布!
  • 足球青训俱乐部后台:Spring Boot开发策略
  • SOMEIP_ETS_144: SD_Reserved_Field_Endpoint_Option_set
  • stm32单片机学习 - MDK仿真调试
  • u盘格式化后数据能恢复吗?2024年Top4恢复神器来帮忙
  • 基于JAVA+SpringBoot+Vue的电商平台的设计与实现
  • 第十讲-显示控件QLabel