【项目日记(八)】第三层: 页缓存的具体实现(下)

简介: 【项目日记(八)】第三层: 页缓存的具体实现(下)

1. 前言

请先看完页缓存的具体实现(上)

本章重点:

本篇文章着重讲解页缓存是怎样把从中心缓存中还回来的内存挂在桶上,并且进行前后页的内存合并的,合并内存形成更大的一份内存来减少内存碎片的问题


2. 什么是内存碎片问题?

我们拿整个程序地址空间来举例:

可以看见虽然整个程序地址空间还有300多byte的空间,但是要申请300byte却申请不出来,对于我们这个项目来说,假设有两个在地址空间中相邻的span,一个是10页,一个是15页,分别挂在第10号桶和第15号桶中,假设没有合并内存此时外部申请一个20页的span是申请不出来的,即使现在空闲的空间有25页,所以这也就体现出了这个项目中合并内存的重要性!


3. 地址空间上的内存使用情况

在地址空间中,一共是4GB大小的空间.

地址从0000 0000到FFFF FFFF.

第0页的起始地址是0
第一页的起始地址是8*1024KB
...以此类推

当中心缓存还回来一个span后,假设这个span中的大页内存在地址空间中是在第1000页,并且span的大小是50页,那么这个还回来的span就处于空闲状态了,此时我们只需要去检查地址空间中第999页的内存是否空闲,如果空闲就将1000~1050页和999页合并,形成一个51页的大块儿span,并且要不断向前合并直到遇见正在使用的页.同理,需要检查地址空间的1051页是否空闲,如果是空闲的,那么就将它一起合并过去!


4. 页缓存合并内存的代码实现

在pagecache.h文件中:

void PageCache::ReleaseSpanToPageCache(SpanData* span)
{
  if (span->_n > N_PAGES - 1)//大于128页的内存直接还给堆,不需要走pagecache
  {
    void* ptr = (void*)(span->_pageid << PAGE_SHIFT);
    SystemFree(ptr);
    //delete span;
    _spanPool.Delete(span);
    return;
  }
  //对span前后的页尝试进行合并,缓解外碎片问题
  while (1)//不断往前合并,直到遇见不能合并的情况
  {
    PAGE_ID prevId = span->_pageid - 1;
    auto prevret = _idSpanMap.find(prevId);
    if (prevret == _idSpanMap.end())//前面没有页号了
      break;
    SpanData* prevspan = ret;
    if (ret == nullptr)
      break;
    if (prevspan->_isUse == true)//前面的页正在使用
      break;
    if (prevspan->_n + span->_n > N_PAGES - 1)//当前页数加上span的页数大于128了,pagecache挂不下了
      break;
    //开始合并span和span的前面页
    span->_pageid = prevspan->_pageid;
    span->_n += prevspan->_n;
    _spanList[prevspan->_n].Erase(prevspan);//将被合并的页从pagecache中拿下来
    //delete prevspan;//将prevspan中的数据清除,诸如页号,页数等
    _spanPool.Delete(prevspan);
  }
  while (1)//不断往后合并,直到遇见不能合并的情况
  {
    PAGE_ID nextId = span->_pageid + span->_n;
    auto nextret = _idSpanMap.find(nextId);
    if (nextret == _idSpanMap.end())//前面没有页号了
      break;
    //SpanData* nextspan = nextret->second;
    auto ret = (SpanData*)_idSpanMap.get(nextId);
    if (ret == nullptr)
      break;
    SpanData* nextspan = ret;
    if (nextspan->_isUse == true)//前面的页正在使用
      break;
    if (nextspan->_n + span->_n > N_PAGES - 1)//当前页数加上span的页数大于128了,pagecache挂不下了
      break;
    //开始合并span和span的前面页
    span->_n += nextspan->_n;
    _spanList[nextspan->_n].Erase(nextspan);//将被合并的页从pagecache中拿下来
    //delete nextspan;//将prevspan中的数据清除,诸如页号,页数等
    _spanPool.Delete(nextspan);
  }
  //合并完后将span挂起来
  _spanList[span->_n].PushFront(span);
  //合并完后,要重新将这个span的首尾两页的id和这个span进行映射,方便别的span来合并我的时候使用
  _idSpanMap[span->_pageid] = span;
  _idSpanMap[span->_pageid + span->_n - 1] = span;
  span->_isUse = false;
}

对于代码的解释都在注释当中,大家可以发现整个合并内存的过程中,我们已经将delete操作符替换为了定长池中的free,这也就是完全脱离了free函数,并且当合并后的页数大于了128,此时整个页缓存哈希桶是挂不下的,所以要特别注意这一种情况


5. 总结以及对代码的拓展

页缓存结构的讲解已经结束,现在回头来看前面设计的这三层缓存结构,可谓是非常之巧妙,第一层线程缓存是无锁的,申请/释放内存非常高效,而第二层中心缓存是用的桶锁,在大多数情况下也没有竞争锁的问题,效率也非常高,所以现在能理解为什么要设计三层而不是两层,甚至是一层,一方面是为了效率的考量,另一方面是为了可以方便合并相邻的空闲页

对代码的拓展:

在使用到了直接向系统返还内存的函数:

inline static void SystemFree(void* ptr)
{
#ifdef _WIN32
  VirtualFree(ptr, 0, MEM_RELEASE);
#else
  // sbrk unmmap等
#endif
}

同样,这份代码知道就行了,不需详谈


🔎 下期预告:项目的测试以及优化🔍


相关文章
|
29天前
|
缓存 开发框架 移动开发
uni-app:下载使用uni&创建项目&和小程序链接&数据缓存&小程序打包 (一)
uni-app 是一个跨平台的开发框架,它允许开发者使用 Vue.js 来构建应用程序,并能够同时发布到多个平台,如微信小程序、支付宝小程序、H5、App(通过DCloud的打包服务)等。uni-app 的目标是通过统一的代码库,简化多平台开发过程,提高开发效率。 在这一部分中,我们将逐步介绍如何下载和使用uni-app、创建一个新的项目、如何将项目链接到小程序,以及实现数据缓存的基本方法。
|
2月前
|
缓存 NoSQL Java
瑞吉外卖项目笔记+踩坑2——缓存、读写分离优化
缓存菜品、套餐数据、mysql主从复制实现读写分离、前后端分离
瑞吉外卖项目笔记+踩坑2——缓存、读写分离优化
|
3月前
|
缓存 开发框架 .NET
看看 Asp.net core Webapi 项目如何优雅地使用内存缓存
看看 Asp.net core Webapi 项目如何优雅地使用内存缓存
|
3月前
|
存储 缓存 开发框架
看看 Asp.net core Webapi 项目如何优雅地使用分布式缓存
看看 Asp.net core Webapi 项目如何优雅地使用分布式缓存
|
5月前
|
敏捷开发 缓存 测试技术
阿里云云效产品使用问题之构建Vue3项目,怎么让node_modules缓存下来
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
6月前
|
缓存
【项目日记(七)】第三层: 页缓存的具体实现(上)
【项目日记(七)】第三层: 页缓存的具体实现(上)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
68 6
|
8天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
66 22

热门文章

最新文章