CMU15445 (Fall 2023) Project 1 - Buffer Pool 思路分享

news2024/11/16 7:54:29

文章目录

    • 写在前面
    • Task 1 - LRU-K Replacement Policy
    • Task 2 - Disk Scheduler
    • Task 3 - Buffer Pool Manager
      • NewPage
      • FetchPage
      • UnpinPage
      • DeletePage
      • FlushPage
    • 写在最后

写在前面

操作系统为应用程序提供了默认的缓存机制,DBMS作为应用程序,为什么不使用默认的缓存机制呢?因为DBMS需要对数据进行(1)空间控制:将经常使用的page写入到磁盘的连续位置,预读取page, (2)时间控制:何时将数据写回磁盘,何时读取数据到内存,使得磁盘I/O次数最少。为此,DBMS需要高度定制缓存策略。

在Project 1中,你需要实现DBMS的缓存池模块(Buffer Pool Manager),定制缓存策略,负责管理数据在磁盘和内存之间的流动。

Task 1 - LRU-K Replacement Policy

假设内存只能存储n个page,那么我们需要使用n个entry(条目)以表示位于内存中的page. 在Replacer中,通常用frame代替entry这个概念。并且用一个整数(frame_id_t)唯一标识frame. 因此我们只需要维护frame_id_t与page的访问记录,BusTub用std::unordered_map<frame_id_t, std::shared_ptr<LRUKNode>> node_store_表示它们之间的映射关系。

BusTub用LRUKNode描述page的访问记录,用LRUKReplacer描述LRU-K策略替换器。在实现具体函数之前,你需要熟悉这两个类的每个成员变量的含义。

此外,你还需要理解k-instance这一概念:当前时间戳与倒数第k次时间戳之差。这个“当前时间”指的是不断流动的绝对时间,而不是最后一次访问page的时间。k-instance随着时间而不断增大,由于所有frame的k-instance都在以同样的速度增加,我们可以忽略这部分增量,用倒数第k次时间戳表示k-instance.

比如k=2时,以下访问序列将产生的驱逐顺序:
image.png
图片来自今天要努力打游戏的博客。

如果你被“之差”误导,你可能会以最后一次访问page的时间戳,与倒数第k次访问page的时间戳“之差”表示k-instance. 这其实是错误的,你没有理解当前时间戳这一概念。

我的具体实现也是用倒数第k次访问page的时间戳表示k-instance, 时间戳越小,表示访问page的时间越早,越有可能被淘汰。因此我用-inf初始化k-instance。注意,这和文档中的描述不同,文档说:k-instance越大,越可能被淘汰。而你应该根据你的具体实现,决定k-instance的大小和被淘汰之间的关系。

值得注意的是LRUKReplacer的size表示可驱逐page的数量,即LRUKNode的is_evictable_为true. 我们需要通过SetEvictable()设置page的is_evictable_,以下是SetEvictable的方法签名:

void LRUKReplacer::SetEvictable(frame_id_t frame_id, bool set_evictable)

当set_evictable为true时,需要将page设置为可驱逐。反之则设置成不可驱逐。而node_store_表示所有位于内存中的page, 包括了可驱逐/不可驱逐。调用Evict()时,我们需要从可驱逐的page中选择一个k-instance最小的page,将其驱逐。显而易见的是,我们需要用其他结构存储不可驱逐的page. 可以是std::priority_queue, 也可以是std::set. 又由于Remove()将驱逐指定page, 所以我们可以选择std::set.

shared_ptr<LRUKNode>为key, 并实现其比较器:如果LRUKNode的k-instance不等,需要满足k-instance的<比较。否则(k-instance只有+inf才会相等),需要满足最早访问时间戳的<比较。这样的话,set的begin()将返回最应该被驱逐的LRUKNode.

你可能会感到疑惑:比较器只实现了<比较,那么find将如何查找相等的key? 比较器可没有实现==比较。实际上,实现了<比较后,我们就可以用以下代码实现==比较:

!Comp(current_element, target_element) && !Comp(target_element, current_element)

当然,你还需要实现时间戳,可以使用依赖硬件,使用系统时钟。但是系统时钟精确度不够,校准后可能产生问题,使得时间戳不是一个递增序列。你也可以使用64位整数表示时钟,每一次page的访问都将使该整数自增。

至于其他注意点,文档已经详细说明了。最后说说RecordAccess()的实现吧:

·检查frame_id是否合法(不超过replacer_size_)
·如果合法,在node_store_中find frame_if
  ·如果page已经存在,维护其访问记录history_, 注意k-instace的维护。
    ·如果page可驱逐,且需要更新k-instance,需要先从set中erase,再insert到set中。直接修改LRUKNode不会触发set的调整,所以你只能手动删除再插入
  ·如果page不存在,make_shared, 向node_store_中插入新的访问记录,当然你需要对LRUKNode进行初始化

Task 2 - Disk Scheduler

disk scheduler使用共享队列(share queue)接收disk requests, 并以先进先出的方式处理这些请求。disk scheduler将启动一个后台线程,用于监听share queue,处理disk requests.

BusTub用DiskRequest描述磁盘请求:

  • is_wirte_:读/写
  • data_:从磁盘读取数据保存到data_/将data_中的数据写入到磁盘
  • page_id_:读取/写入的页号
  • callback_:std::promise<bool>,表示请求是否被处理

用DiskScheduler描述disk scheduler:

  • construct:用std::optional.emplace()原地创建后台线程,并使之执行StartWorkerThread函数
  • destruct:向share queue中发送std::nullopt,告知调用StartWorkerThread的线程返回
  • Schedule():向DiskManager发送DiskRequest
  • StartWorkerThread():后台线程需要执行的逻辑。负责获取share queue中的请求并处理:调用Schedule()将DiskRequest发送给DiskManager。如果Schedule()成功,设置DiskRequest的callback_为true,使请求发送方得到“请求被处理”的信息。该函数总是在运行(while(true)),直到DiskScheduler的生命周期结束

要写的代码不多,不到20行。理清DiskRequest, DiskScheduler, DiskManager之间的关系即可。不过在StartWorkerThread()中,你应该创建一个子线程,使之调用Schedule()处理DiskRequest, 并detach子线程。因为读写操作会占用大量时间。

Task 3 - Buffer Pool Manager

至此,基于前两个Task,我们终于凑齐了Buffer Pool的组件。其中,我们不需要实现磁盘的读取/写入方法,BusTub已经将这些方法封装为DiskManager, 我们只需要使用DiskScheduler调用DiskManager即可。当然,如果你对DiskManager感兴趣,想知道如何与磁盘打交道,那么去看看它的具体实现吧: )。

Page用来描述操作系统的内存资源,是BusTub中资源分配的基本单位。用来保存从磁盘读取,或是将要写入磁盘的数据。Page是一个4KB大小的内存块(BUSTUB_PAGE_SIZE = 4096),数据在磁盘和内存间流动时,我们将重用相同的Page存储不同的物理页(一般情况下,物理页的大小也是4KB)。

当然,你应该仔细阅读文档,了解其pin_count_, is_dirty_字段的具体含义。

回到BufferPoolManager中,其中有一些需要重点关注的成员:

  • pool_size_:缓存池大小,表示page/frame的数量。因此frame_id的范围只能在[0, pool_size_ - 1]之间
  • pages_[]:类型为Page的数组,在构造函数中用pool_size_个page填充。也就是说,page资源已经提前获取了。同时frame_id将作为pages_的下标,用来访问对应的page资源
  • free_list_:未被使用的frame_id. 如果大小为n, 说明当前DBMS还能缓存n个page
  • page_table_:page_id与frame_id的映射,如果page被写回磁盘,其frame_id应当被设置为-1.

此外还有两个函数:AllocatePage()与DeallocatePage(),前者用于获取page_id, page_id是一个递增序列,和时间戳一样。后者则用于释放page_id, 不过BusTub没有维护被释放的page_id, 比如page_id被使用了0, 1, 2, 3, 4, 现在释放1, 3. 由于BusTub没有维护被释放的1, 3, 所以我们只需要象征性地调用该函数。

接着是需要实现的函数,文档对这部分的说明很简略,你应该阅读.h文件中的注释,我会给出更详细的逻辑。但是在开始前,强调一点:page可能位于磁盘,每个函数都需要考虑Page在磁盘中的情况。特别是Fetch一个在磁盘的Page.

NewPage

创建新的page, 返回其指针与page_id:
auto NewPage(page_id_t *page_id) -> Page *
·判断是否存在存在frame
  ·若不存在,试着evict frame
    ·若evict失败,返回nullptr
    ·若成功evict, 且page is dirty, 你需要刷脏,以及重置Page的data(ResetMemory), 维护page_table_
·成功获取frame后,你需要初始化Page的相关字段
·如AllocatePage()获取page_id, RecordAccess()记录对page的访问,SetEvictable()设置不可驱逐,维护page_table_

获取空闲frame后,如果你不知道如何获取Page *:通过pages_ + 空闲的frame_id即可

以上,你还需要注意:刷脏后,应该维护page_table_使该page指向-1, 表示page位于磁盘。此外,还有一个无关紧要的地方:(1)刷脏时,需要通过DiskRequest的promise获取其future, 判断是否刷脏成功(一般都会成功)

FetchPage

根据page_id获取指定的页:
auto FetchPage(page_id_t page_id) -> Page *;
·find page_table_
·若存在,分为两种情况(1)page在内存中 (2)page在磁盘中
  (1)若page在内存中,SetEvictable(因为该page被使用了,设置不可驱逐), pin_count_++
  (2)若page在磁盘中,需要将其加载到内存
  ·检查是否存在空闲frame
    ·若不存在,试着evict frame
      ·若evict失败,返回nullptr
      ·若成功evict, 且page is dirty, 你需要刷脏,以及重置Page的
  ·成功获取frame后,你需要初始化Page的相关字段,当然你需要SetEvictable与pin_count_++
  ·构造read DiskRequest读取page数据到Page中,将其返回

UnpinPage

释放page的pin_count_
auto UnpinPage(page_id_t page_id, bool is_dirty) -> bool;
·如果page_id不存在,或pin_count_已经为0,返回false
·通过page_id与page_table_获取frame_id,进一步可以获取Page *
·pin_count_--,如果pin_count_为0,SetEvictable(设置page为可驱逐)
·设置Page的is_dirty_

当然,如果修改了page,则需要设置is_dirty参数为true.

DeletePage

删除内存中指定的page
·auto DeletePage(page_id_t page_id) -> bool;
·find page_table_: 没有必要删除不存在或者在磁盘中的page
·如果Page不可驱逐(pin_count_ != 0),return false
·replacer_->Remove()在LRUKReplacer删除page使用的frame, page_table_.erase(), free_list_.push_back
·重置Page的data_, 与剩下字段
·最后象征性地调用DeallocatePage()

FlushPage

手动刷脏
auto FlushPage(page_id_t page_id) -> bool
·find page_table_, 若无法找到,返回false
·无论是否为脏页,都构造DiskRequest的写请求,写入数据
·数据写入完成将Page.is_dirty_设置为false

最后,关于并发控制,给每个函数加一把大锁就好了: )
std::lock_guard<std::mutex> lk(this->latch_);

写在最后

如果你对文章的某些描述感到疑惑,或是发现了文章的错误,欢迎在评论区提出: )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1992238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024年中级消防设施操作员(考前冲刺)证模拟考试题库及中级消防设施操作员(考前冲刺)理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年中级消防设施操作员&#xff08;考前冲刺&#xff09;证模拟考试题库及中级消防设施操作员&#xff08;考前冲刺&#xff09;理论考试试题是由安全生产模拟考试一点通提供&#xff0c;中级消防设施操作员&#…

7.2 继承与多态:Python 面向对象编程的魔法

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

idea中怎么使用git把项目提交到远程仓库

git的版本控制 在gitignore中添加这些文件 可以在与远程仓库进行操作的时候 忽略掉这些文件夹 使用Git进行项目代码的版本控制&#xff0c;具体操作&#xff1a; 1). 创建Git本地仓库 当Idea中VS变成Git(G)&#xff1a; 说明本地仓库创建成功。 2). 创建Git远程仓库 1、gi…

Deep-Live-Cam:只需单张图像即可实现人脸替换;零一万物、月之暗面再掀国产大模型资本战丨 RTE 开发者日报

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、…

萤石开放平台开发票流程

若您已经在开放平台完成充值&#xff0c;可通过以下方式自助开票 一、PC端开票 进入开放平台首页&#xff0c;登入账号&#xff0c;进入价格页面&#xff0c;点击首页右下方的“开票指引”入口 二、萤石云视频APP 萤石云APP&#xff1a;底部导航栏“我的”——点击“订单”…

如何编写一个有效的OKR(带有示例)

客户经常问我们的一个问题是&#xff1a;”我如何写出有效的目标和关键结果&#xff1f; 我如何写出有效的目标和关键结果&#xff1f; 一开始&#xff0c;要弄清楚不同的要素是很困难的&#xff0c;而且有这么多的指导和风格&#xff0c;你很难知道自己是否做对了。 在这篇…

【Nacos无压力源码领读】(三) Nacos 配置中心与热更新原理详解 敢说全网最细

本文将从 Nacos 配置中心的基本使用入手, 详细介绍 Nacos 客户端发布配置, 拉取配置, 订阅配置的过程以及服务器对应的处理过程; 配置订阅以及热更新原理相关的部分, 我看了主流的博客网站, 绝对没有比这更详细的讲解; 如果在阅读过程中对文中提到的 SpringBoot 启动过程以及…

k8s 与 docker 安装 Syncthing 文件同步服务器

Syncthing是一个开源文件同步工具&#xff0c;可以在多台设备之间实时同步文件或文件夹&#xff0c;官方网站&#xff1a;https://syncthing.net/ 下载地址&#xff1a;https://syncthing.net/downloads/ &#xff0c;如果是windows一般推荐下载图形界面SyncTrayzor, 但我这边都…

视频循环存储的实现

目录 1. 三方工具 2. 视频存储的实现 2.1 分段存储 - 比如每15分钟 2.2 对齐到15分钟整边界 2.3 循环存储的实现 video_space_daemon.sh 3.封装 3.1 主执行程序&#xff0c;修订版 3.2 创建服务 3.3 service关联的执行脚本文件 4.额外的工作 附录A: ffmpeg视频存储…

中电金信三步法全面助力银行数字化营销体系建设

存量用户竞争时代 精细化经营、个性化服务与多场景覆盖 成为银行经营的重要策略 营销数字化转型不可或缺 但是&#xff0c;与所有转型的曲折、阵痛等特征一样&#xff0c;银行构建数字化营销运营体系过程中&#xff0c;亦走过一些弯路&#xff0c;包括&#xff1a; 缺少顶层…

收银系统源码-连锁店版本

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 私有化独立部署/全开源源码&#xff0c;系统开发语言&#xff1a; 核心开发语言: PHP、HTML…

【漏洞复现】某赛通电子文档安全管理系统 PolicyAjax SQL注入漏洞

0x01 产品简介 某赛通电子文档安全管理系统&#xff08;简称&#xff1a;CDG&#xff09;是一款电子文档安全加密软件&#xff0c;该系统利用驱动层透明加密技术&#xff0c;通过对电子文档的加密保护&#xff0c;防止内部员工泄密和外部人员非法窃取企业核心重要数据资产&…

医药行业如何对内部机密数据进行加密保护?

一、医药行业加密解决方案 1、药品研发过程中加密保护重要数据&#xff0c;防止信息泄露 考虑到药品研发部门数据安全重要性高&#xff0c;且与其他部门较少业务关系往来&#xff0c;为防止公司研发配方泄密到其他部门或者公司外&#xff0c;研发部门实行权限控制。做到研发部…

JESD204B/C协议学习笔记

JESD204B基础概念 204B包含传输层&#xff0c;链路层&#xff0c;物理层。 应用层是对 JESD204B 进行配置的接口&#xff0c;在标准协议中是不含此层&#xff0c;只是为了便于理解&#xff0c;添加的一个层。 协议层指工程中生成的IP核JESD204B&#xff0c;负责处理输入的用户…

pod详解 list-watch机制 预选优选策略 如何指定节点调度pod

K8S是通过 list-watch 机制实现每个组件的协同工作 controller-manager、scheduler、kubelet 通过 list-watch 机制监听 apiserver 发出的事件&#xff0c;apiserver 也会监听 etcd 发出的事件 scheduler的调度策略&#xff1a; 预选策略&#xff08;Predicates&#xff09;…

JavaScript中的__setitem__方法

1、问题背景 Python中存在一个名为__setitem__的方法&#xff0c;该方法能够在向对象中设置值时对其进行处理。例如&#xff0c;以下代码演示了如何在Python中使用__setitem__方法对一个字典中的键值对进行平方处理&#xff1a; class CustomDict(dict):def __setitem__(self…

PMP证书3A一次通过攻略(内含血泪教训和踩过的坑,避雷必看)

手把手教你如何3A速通PMP证书 首先&#xff1a;你考PMP到底图什么&#xff1f; 清晰、强烈的动机是意志力的基础&#xff0c;你是想要加薪升职&#xff1f;转行转岗&#xff1f;还是只是为了知识&#xff1f;无论什么理由都可以&#xff0c;当搞明白到底图什么&#xff0c;学习…

URDF在线可视化网站

​ URDF全称&#xff08;United Robotics Description Format&#xff09;统一机器人描述格式&#xff0c;是一个XML语法框架下用来描述机器人的语言格式&#xff0c;URDF在ROS界很流行。 关于URDF的详细介绍&#xff0c;请参考博文URDF学习&#xff08;一&#xff09;什么是U…

海康gige工业相机无驱动取像突破(c#实现,版本更新,你也可以移植到linux下去用)

我们前面有一个版本&#xff0c;没有整理&#xff0c;只能是500万海康gige工业相机无驱动取像成功&#xff08;黑白相机gm&#xff09;。 这里&#xff0c;版本更新了&#xff0c;可以不是500万&#xff0c;200万&#xff0c;80万也可以&#xff0c;你可以去试一试&#xff0c…

【C++二分查找 树状数组】2424. 最长上传前缀

本文涉及的基础知识点 C二分查找 树状数组 LeetCode2424. 最长上传前缀 给你一个 n 个视频的上传序列&#xff0c;每个视频编号为 1 到 n 之间的 不同 数字&#xff0c;你需要依次将这些视频上传到服务器。请你实现一个数据结构&#xff0c;在上传的过程中计算 最长上传前缀…