CMU15445-2024fall-project1踩坑经历

p1目录:

  • lRU\_K替换策略
    • LRU
    • LRU\_K
    • 大体思路
      • SetEvictable
      • RecordAccess
      • Size
      • Evict
      • Remove
  • Disk Scheduler
  • BufferPool
    • NewPage
    • DeletePage
    • FlashPage/FlashAllPage
    • CheckReadPage/CheckWritePage
      • PageGuard
      • 并发设计
      • 主逻辑

感谢CMU的教授们给我们分享了如此精彩的一门课程, 希望您能尊重教授们和TAs的劳动成果!

本篇文章记录本人对实验中各个板块的理解以及踩坑, 如果您发现我过多的涉及到了实验的内容, 有违学术诚信, 请告诉我!

正文:

Project1要实现的是一个Buffer pool Manager。 Buffer pool Manager的作用是能够对磁盘中的数据进行预缓存。 数据库的数据是保存在磁盘中的, 要对其中的数据进行处理, 就要将磁盘的数据加载到内存中, 但是如果当每次要用这个数据的时候再从磁盘中取出, 那么就会消耗大量的时间。 Buffer Pool Manager的作用就是预先从磁盘中将数据取出来, 当需要使用到时候再把数据从内存中直接提取使用。

在本project中, 我们要实现三个组件:

  • lru_k替换策略

  • Disk Scheduler 磁盘管理器

  • Buffer pool Manager 缓冲池管理器。

lRU_K替换策略

Buffer pool manager通过lru_k的替换策略来替换掉缓冲池中的不常用页面。 这个lru_k的替换策略类似于操作系统中的三大调度算法 —— 进程替换、页面置换、 磁盘调度算法。

为了解释lru_k, 这里用lru进行辅助理解。 下面是两者的执行策略和思想:

LRU

  • 执行策略:选择最长时间没有被使用过的页面进行替换。
  • 思想:过去访问频繁的页面, 未来也会频繁访问;过去访问最少的页面, 未来也会很少访问。

如果两个数据的访问次数相同, 那么删除访问时间最早的那一个。 或者说是访问时间距离现在最远的那一个。

LRU_K

  • 执行策略: 设置一个k, 然后规定一个后向距离k, 删除后向距离k最大的节点。

    • 在这里插入图片描述

    • 后向距离k的计算方式为(除红框框外的论述):
      
    • 当节点被访问的次数大于等于k:k = 当前时间戳 - 第前k次访问该节点时的时间;

      • 比如当前时间戳为1000, k为3。有节点1、节点2。其中节点1的访问次数为4, 访问时间戳依次为:100, 200, 300, 400; 节点2的访问次数为5, 访问时间戳依次为:10, 20, 50, 800, 900。那么对于节点1的后向k为: 1000 - 200 = 800。 节点2的后向k为: 1000 - 50 = 950。 所以删除节点2。
    • 当节点被访问的次数小于k:k = +inf, 如果两个节点的访问次数均小于k, 那么会删除第一次时间戳距离当前时间戳最久的节点。

        • 对于当访问次数小于k的时候我之前也有过疑问, 文档中写的意思按照我的理解是:删除节点中最近访问的时间戳最小的节点, 但是我这么写线上测试没过。 所以这里的节点访问次数小于k应该是对于最早的一次访问的时间戳与当前时间戳的比较。
    • 综上lru_k的策略其实很简单: 就是如果有访问次数小于k的节点,优先删除访问节点小于k的, 如果存在多个这样的节点, 那么就删除第一次访问的时间戳最早的节点。 如果没有访问次数小于k的节点, 那么计算后向k距离, 删除后向k距离最大的节点。

大体思路

LRU_K的代码集中在了lru_k_replacer.h和cpp文件中。您可以阅读lru_k_replacer.h中的注释和类的定义来获取实现所需要的各种信息。

在这里插入图片描述

(上图为官方仓库)LRUKNode是lru_k中的节点。根据注释的描述, histroy_用来存储访问的timestamps(时间戳)、k_为规定的k、 fid_为与当前节点绑定的缓冲池中的页面id、is_evicatable_为当前页面是否可以被删除。

另外, 在该项目中, 我们的变量如果定义后没有使用, 编译是会报错的, 所以请将不使用的变量删掉或者不确定使用可以加[[maybe_unused]]修饰。

在这里插入图片描述

(上图为官方仓库)LRUKReplacer是lru_k数据结构, 作为移除器使用。 这里面可能有一些我们可能需要的属性以及要实现的方法。

SetEvictable

修改指定的lru_k节点是否可以删除。 可以为true, 否则为false。需要注意同时修改lru_k移除器中的可移除节点的数量。

RecordAccess

页面被访问时调用, 更新页面对应节点的使用情况。先查找lru_k移除器有没有维护该页面。 如果没有, 则添加维护信息; 如果已经维护, 添加时间戳, 同时全局时间戳要增加。

Size

返回移除器中可移除节点的数量。

Evict

lru_k板块的核心。 找出要移除的节点。 大体思路就是设置一个指向删除节点的标记位。遍历所有可移除节点, 计算后向k距离, 与被标记的节点对比。 如果两个后向k都为inf, 标记两者中最小时间戳更小的那一个; 如果其中一个inf, 标记该节点。 如果两个都不为inf, 标记后向k更大的。

Remove

Remove也是移除某个节点。 不同的是, 它是指定某个可移除帧, 确定该帧要被删除。而Evict是从可以出帧中选出一个不常用的删除。 Remove面向外部的 “我要删除特定的帧”; Evict面向外部的"我需要一个帧”。

Disk Scheduler

这部分记不太清了, 印象中实现起来很简单。 难点是要使用C++17的语法promise 和 future, 学习一下新语法就可以通过了。

BufferPool

实现BufferPool, 其实就是实现PageGuard的创建和删除, 在该组件中, 核心的功能就是 创建新页面、从磁盘读取页面、删除页面, 将页面刷新到磁盘。

NewPage

NewPage, 即创建新页面。 就是在磁盘中创建新的页面。 注意!不是将页面放到缓冲池的frames中, 而是直接在磁盘上创建一个页面, 无需读取到frames。利用DiskScheduler的函数。

DeletePage

删除页面的操作,这个删除是在磁盘中删除, 而不是只从缓冲池中删除。如果目标页面在缓冲池的帧中, 那么刷新到磁盘, 再从磁盘中删除。 注意:只从缓冲池删除p1不会测试出现问题, 但是在p3的外排序任务中需要检测磁盘的刷新和加载。

FlashPage/FlashAllPage

将页面刷新到磁盘。(刷新磁盘的操作都是使用Disk Scheduler中的刷新函数)

CheckReadPage/CheckWritePage

p1的核心, 最难的地方。 需要理解pin_count的增加和减少的时机、SetEvict的时机;以及大锁(整个BufferPool的锁)和小锁(单个页面的读写锁) 的获取与释放的时机。

我们下面就先讨论并发设计:

PageGuard

对于CheckReadPage和CheckWritePage, 他们两个获取的是指定page_id的PageGuard。 这个PageGuard, 可以理解为BufferPool内frame帧的RAII管理器。 创建ReadPageGuard的时候, 会增加帧的引用计数, 修改帧的lru_k属性, 同时也会获取帧的读锁, 此时如果获取了读锁, 那么别的线程再获取写锁,即WritePageGuard, 就会阻塞;创建WritePageGuard的时候, 同样也会增加帧的引用计数, 修改帧的lru_k属性, 同时也会获取帧的写锁, 此时如果获取了写锁, 那么别的线程再获取ReadPageGuard或者WritePageGuard, 就会阻塞。

当ReadPageGuard释放的时候, 会减少帧的引用计数, 修改帧的lru_k属性, 同时会释放读锁,销毁资源; 当WritePageGuard释放的时候, 同样会减少帧的引用计数, 修改帧的lru_k属性, 同时会释放写锁。

以上就是PageGuard的理解。

并发设计

并发设计要保证三个顺序:

  • 当获取和释放PageGuard的时候, 对于读写锁和pin_count,我们要保证一个顺序:pin->lock->unlock->unpin。

    • 大概的主要原因是因为如果lock->pin, 那么在线程获取小锁被锁住后,该页面的pin_count只有1, 如果持有页面的线程释放该页面, 在lock -> pin + SetEvict()中间的空挡, 该页面可能会被设置为可移除,进而被刷新回磁盘。那么线程再lock, lock的是什么? 我们要知道, lock是frame帧里面的成员, 作用是锁住该帧。 而如果原本的页面被替换成别的页面, 那么此时lock,锁住的就不是原本的页面, 而是新替换来的页面了, 与预期结果不符, 程序出错。(TODO。这里不太懂,可能就是先 lock再pin会导致页面被刷新替换, 导致lock了自己不想要的页面,导致逻辑错误。)
  • 保证获取小锁之前要将大锁释放掉。在本地测试的DeadLockTest有下面这种情况:

    • 线程TA想要获取页面A, 所以进入CheckPage函数, 获取了大锁, 然后创建页面A的PageGuard获取页面A的小锁。退出CheckPage函数释放大锁。
    • 此时线程TB也想要获取页面A, 所以进入CheckPage函数, 获取了大锁, 然后创建页面A的PageGuard时因为A的锁被占用, 所以阻塞。 此时TB占据着大锁阻塞。
    • 如果此时TA想要再获取任何一个页面, 都要进入CheckPage函数获取大锁, 但是大锁被TB占用, 就形成了一种情况:TA占据着页面A的小锁, 想要获取TB占用的的大锁; TB占据着大锁, 想要获取TA占用的小锁。 形成死锁。
    • 所以我们要确保TB在阻塞之前把大锁释放掉。 但是这样我们必须说服自己一件事, 就是这样做可能导致插队,此时执行会不会不符合预期?
    • 如果TA和TB两个获取不同的页面PageGuard那么不会造成插队情况, TA获取页面A的PageGuard之前释放大锁, TB进入获取TB的PageGuard。两个线程针对不同的frame,即BufferPool中不同的内存块。互相不干预, 而且因为减少了锁粒度,可以让并发更快。
    • 如果TA和TB获取相同的页面, TA获取页面A的PageGuard,在获取小锁之前释放大锁;TA刚刚释放大锁, 此时一个TB速度极快, 迅速获取大锁, 然后抢在TA之前获取小锁。
    • 如果TA和TB获取的都是读锁, 那么不冲突, 两者都可以获取到页面A的小锁。
    • 如果TA获取读锁, TB获取写锁。或者TA获取写锁, TB获取读锁。 那么TA会被插队, TB会成功获取到页面A。 此时的结果相当于TB获取到页面A后,TA才执行。页面A此时pin_count为二,TBpin了一次, TApin了一次,并且页面A的可移除状态为false。 结果在可接受范围之内。
  • 保证pin/unpin和SetEvictable()被绑定为原子。 即两个操作一定是在一起,原子的。所以要保证pin和SetEvictable()以及unpin 和SetEvictable()是在大锁的保护下。 因为如果pin和SetEvictable不被绑定为原子操作,就会出现以下这种情况:

    • 对于页面A,线程A本来持有该页面。然后 线程Aunlock->unpin->pin_count == 0。 线程A想要进入SetEvictable。
    • 此时另一个线程B在正在获取页面A, 并且执行速度非常快。 很快就pin->pin_count == 1了。然后比线程A更快进入SetEvictable, 修改了页面的状态变成”不可移除“。
    • 最后线程A才进入SetEvictable修改页面变成”可移除“。程序出错。

所以要保证pin和unpin都是在锁保护下的,我们就可以在这里使用大锁进行保护。 即 获取大锁->pin->释放大锁->lock->unlock->获取大锁->unpin->释放大锁。

主逻辑

注释中给出的解释非常清楚,为了节省看文章的时间, 这里直接说这段注释的意思:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

意思概括起来大概就是说:

  • 其中一种情况是无需IO, 也就是说此时页面就在内存中。(即帧中)
  • 第二种情况是内存够用。(即帧有空闲位)
  • 第三种情况是页面既没在内存中, 内存也不够用了, 此时需要使用页面置换算法, 替换不常用页面。

情况一直接查找,如果在页面中, 更新lru_k时间戳获取页面就行了。

情况二查找不在页面,但是有空闲, 那么直接从磁盘拿页面,设置 帧的页面id , 维护缓冲池内部 页面管理表(page_table_) ,添加页面到lru_k替换器。

情况三查找不在页面, 并且没有空闲。 要先使用Evict查找最少使用页面并从lru_k替换器中移除,从帧中移除之前先将页面数据刷新到磁盘, 然后再替换新的页面进入该帧, 设置 帧的页面id , 维护缓冲池内部 页面管理表(page_table_) , 添加新页面到lru_K替换器。

以上。暂时先写成这样。
参考文章:

https://zhuanlan.zhihu.com/p/828933572

https://blog.csdn.net/John_Snowww/article/details/145908700

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

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

相关文章

【C语言进阶】带你由浅入深了解指针【第四期】:数组指针的应用、介绍函数指针

前言上一期讲了数组指针的原理,这一期接着上一期讲述数组指针的应用以及数组参数、函数参数。首先看下面的代码进行上一期内容的复习,pc应该是什么类型?char* arr[5] {0}; xxx pc &arr;分析:①首先判断arr是一个数组&#x…

在HTML中CSS三种使用方式

一、行内样式在标签<>中输入style "属性&#xff1a;属性值;"。(中等使用频率)不利于CSS样式的复用&#xff1b;违背了CSS内容和样式分离的设计理念&#xff0c;后期难以维护。<p style"color: red">这是div中的p元素</p>二、内部样式在…

汽车功能安全-软件单元验证 (Software Unit Verification)【用例导出方法、输出物】8

文章目录1 软件单元验证用例导出方法2 测试用例完整性度量标准3 验证环境要求4 软件单元验证的工作产品1 软件单元验证用例导出方法 为确保软件单元测试的测试案例规范符合9.4.2要求&#xff0c;应通过表8所列方法开发测试用例。 表8 软件单元测试用例的得出方法&#xff1a; …

MySQL内置函数(8)

文章目录前言一、日期函数二、字符串函数三、数学函数四、其它函数总结前言 其实在之前的几篇中我们也用到了内置函数&#xff0c;现在我们再来系统学习一下它&#xff01; 一、日期函数 函数名称描述current_date()获取当前日期current_time()获取当前时间current_timestamp(…

苍穹外卖项目日记(day04)

苍穹外卖|项目日记(day04) 前言: 今天主要是接口开发, 涉及的新东西不多, 需要注意的只有多表联查和修改的逻辑,今日难点: 1.菜品的停起售状态设置 2.套餐的停起售状态设置 3.动态sql中的 useGeneratedKeys 与 keyProperty 两个参数 一. 菜品的停起售状态设置 ​ 在菜品的停售中…

React之旅-05 List Key

每个React的初学者&#xff0c;在调试程序时&#xff0c;都会遇到这样的警告&#xff1a;Warning: Each child in a list should have a unique "key" prop. 如下面的代码&#xff1a; const list [Learn React, Learn GraphQL];const ListWithoutKey () > (&l…

[特殊字符] 人工智能技术全景:从基础理论到前沿应用的深度解析

&#x1f680; 人工智能技术全景&#xff1a;从基础理论到前沿应用的深度解析 在这个AI驱动的时代&#xff0c;理解人工智能的核心技术和应用场景已成为技术人员的必备技能。本文将带你深入探索AI的发展脉络、核心技术差异以及在各行业的创新应用。 文章目录&#x1f680; 人工…

Go语言教程-环境搭建

前言 Go&#xff08;又称 Golang&#xff09;是由 Google 开发的一种 开源、静态类型、编译型 编程语言&#xff0c;于 2009 年正式发布。它旨在解决现代软件开发中的高并发、高性能和可维护性问题&#xff0c;尤其适合 云计算、微服务、分布式系统 等领域。 Go 语言国际官网…

windows指定某node及npm版本下载

下载并安装 nvm-windowshttps://github.com/coreybutler/nvm-windows/releases&#xff08;选择 nvm-setup.zip&#xff09;。打开命令提示符&#xff08;管理员权限&#xff09;&#xff0c;安装 Node.js v16.15.0&#xff1a; nvm install 16.15.0 nvm use 16.15.0 验证node版…

每天一个前端小知识 Day 28 - Web Workers / 多线程模型在前端中的应用实践

Web Workers / 多线程模型在前端中的应用实践&#x1f9e0; 一、为什么前端需要多线程&#xff1f; 单线程 JS 的瓶颈&#xff1a;浏览器主线程不仅负责执行 JS&#xff0c;还要负责&#xff1a; UI 渲染&#xff08;DOM/CSS&#xff09;用户事件处理&#xff08;点击、输入&am…

python:ImportError: cannot import name ‘ParameterSource‘ from ‘click.core‘

浏览器访问网站抛错&#xff1a;ImportError: cannot import name ParameterSource from click.core (E:\environment\python\Lib\site-packages\click\core.py)问题分析&#xff1a;1. click 版本问题ParameterSource 可能是在某个特定版本的 click 库中引入的&#xff0c;而你…

flink 去重

LOCALTIMESTAMP as time_stamp ts as case when time is null then CURRENT_TIMESTAMP else TO_TIMESTAMP_LTZ(time, 0) end , watermark for ts as ts - interval ‘60’ second PARTITION BY 的都有回撤流 group by 的没有回撤流 因为算的是指标 开窗又慢 SELECT * FROM (…

【音视频】TS协议解析

参考博客&#xff1a;https://blog.csdn.net/rell336/article/details/38109621?utm_mediumdistribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_sourcedistribute.pc_relevant_t0.none-task-blog-BlogCommendFromMac…

uniapp 日期组件可选择年月

month-picker 月份选择器组件 组件介绍 month-picker 是一个用于选择年月的自定义组件&#xff0c;基于 uni-app 开发&#xff0c;提供了简洁的月份选择功能。 解决弹框底部出现底部页面区域 safe-area属性设为true时&#xff0c;即可解决这个问题效果如图功能特点 支持选择年份…

从真人到数字分身:3D人脸扫描设备在高校数字人建模教学中的应用

在影视、动漫、游戏等数字创意产业蓬勃发展的当下&#xff0c;超写实虚拟数字人凭借其高度逼真的形象&#xff0c;成为行业关注的焦点。无论是影视特效中栩栩如生的角色&#xff0c;还是游戏里精致的NPC&#xff0c;超写实虚拟数字人的制作都离不开先进的技术支撑。而3D人脸扫描…

你以为大数据只是存?其实真正的“宝藏”藏在这招里——数据挖掘!

你以为大数据只是存&#xff1f;其实真正的“宝藏”藏在这招里——数据挖掘&#xff01; 曾经我也天真地以为&#xff0c;搞大数据就是会写几个SQL、部署个Hadoop集群&#xff0c;结果真到项目现场&#xff0c;甲方爸爸一句&#xff1a;“给我挖掘一下用户的购买意图”&#xf…

LeetCode经典题解:128、最长连续序列

“最长连续序列”是一道极具代表性的数组处理问题&#xff0c; 本文将带你从直观思路出发&#xff0c;逐步推导出最优解法&#xff0c;并通过场景化记忆技巧掌握核心逻辑。 一、题目描述 题目&#xff1a;给定一个未排序的整数数组 nums&#xff0c;找出数字连续的最长序列&…

电力分析仪的“双语对话”:CCLinkIE与Modbus TCP的无缝连接

在工业自动化领域&#xff0c;协议兼容性问题如同“方言壁垒”&#xff0c;让不同品牌、不同系统的设备难以高效协同。对于电力分析仪这类关键设备而言&#xff0c;如何打破CCLinkIE与Modbus TCP协议的“语言障碍”&#xff0c;已成为工程师优化系统集成的核心课题。 为何需要协…

暑假复习篇之文本编译器

一、知识点补充【在此次示例代码上显示的关键用法】知识点1、JMenuBar&#xff1a;菜单栏的容器&#xff0c;通常添加到JFrame的顶部。关键用法&#xff1a;add&#xff1a; 添加菜单到菜单栏2、JMenu&#xff1a;菜单条目&#xff08;“文件” “编辑” 等&#xff09;&#x…

Linux自动化构建工具(一)

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录Li…