CMU 15-445 (FALL 2022) Project #1 Buffer Pool题解

前言

最近有空了,做了15-445的第一个lab。因为这个学期网上评测已经结束了,所以还没来得及在线上测试。
Project1 Buffer Pool中有三个小Task,第一个是实现可拓展哈希(网上有很多资料,我觉得也可以用C++已经封装好的容器代替)。第二个是LRU-K算法的实现,在我之前这篇博客中讨论过缓存替换策略:LRU-K算法详解及其C++实现 CMU15-445 Project#1
然后在这里主要讨论第三个Task,BufferPoolManagerInstance的实现。
在这里插入图片描述

题目讲解

  • 在实现缓冲池之前,我们先需要知道两个概念的区别:frame和page。
  • 在这里frame是指在内存中的一块区域,用于存放page。frame在内存中是固定的
  • page是包含了元信息(比如是否是脏页面、page_id等)和page_data的一个类。page可以存放在frame中,也可以将数据写在disk上以便被缓存池丢掉。

属性

在类BufferPoolManagerInstance主要有以下属性

  • pool_size_
    • pool_size_是缓冲池的大小,也是最多frame个数。
  • Page *pages_
    • 指向一个Page数组,用于存放所有的Page。
  • free_list_
    • 一个list,里面存放了空闲的frame_id
  • 还有之前实现的哈希表、lru_k_replacer、打开了当前对应文件的disk_manager
  • BufferPoolManagerInstanceclass Page的友元,可以直接访问Page的属性,我们需要用到Page以下的属性
    • data_是一个字符数组,用于存放数据
    • page_id_
    • is_dirty_ 记录是否是脏页面
    • pin_count_ 记录被pin住的数量,当pin_count==0时,这个页面才能够被驱逐

方法

NewPgImp
  • 功能:为当前的数据库增加一页,并且返回该页。
  • 如果内存中有空闲的frame,(free_list不空)则直接把该页放到这个frame上。否则通过之前的LRU_K策略驱逐一个frame,这里如果frame中的page是脏的,需要用之后的Flush方法写回磁盘。
  • 这里要注意,生成一个Page后记得把它的data_清零,然后驱逐、增加等操作都要影响page_table_和replacer_的数据,记得哈希表去掉旧的page_id并添加新的键值对。
  • 新的page按照题目意思pin_count_为1,不能够被驱逐。
FetchPgImp
  • FetchPgImp传入一个page_id,并返回对应页面的指针。
  • 如果通过hash表查到该page在内存中,直接返回,记得LRU-K记录一下访问历史。
  • FetchPgImp驱逐过程和上一个方法类似。但是要加一步:从磁盘中将之前的data读入内存
    disk_manager_->ReadPage(page_id,pages_[frame_id].GetData());
    注意
  • 如果要fetch的页面已经在pool中了,那么要进行以下操作
    • lru-k要进行record
    • 手动setEvictable为false
    • pincount++
  • 如果不在pool中,那么也要进行以上操作,并且pincount = 1
  • 上面这个卡了我很久,因为在文档里也没写具体的要求,大家写的时候注意一下
UnpinPgImp
  • 如果对应页面pin_count>0pin_count_--
  • 当pin_count为0,设置该页面可被驱逐
FlushPgImp
  • 通过disk_manager的WritePage将页面写入disk中
  • 将脏页面标记设置为false.
FlushAllPgsImp
  • Flush所有页面,注意这里不能写frame为空的位置
  • 可以设置一个标记数组,将free_list遍历一遍找出空的frame,然后再遍历一次buffer_pool,不为空则flush。带来的时间和空间复杂度冗余都是 O ( p o o l s i z e ) O(pool size) O(poolsize),可以接受。
DeletePgImp
  • 删除指定Page

代码实现

  • 在这个Project中我学到了几个c++的用法。
  • Page *pages_; pages_ = new Page[pool_size_];之后,pages_指向一个数组。
  • 在创建新page时,pages_[frame_id]是一个Page类型,不能通过指针创建,比如pages_[frame_id] = new Page()是错的。也不能pages_[frame_id] = Page(),因为Page没有拷贝方法。我们可以通过new(&pages_[free_frame]) Page();在指定位置上构造一个新的Page。这里new里面的参数是一个地址。
  • 同理,pages_[frame_id]不是一个指针,所以我们在删除页的时候,不能使用delete pages_[frame_id],可以通过pages_[frame_id].~Page()来调用析构函数。
    在这里插入图片描述

然后其他的代码实现按照题目要求做就行了,注意每次frame的变更对page_table_、replacer_的影响,遇到错误使用GDB或者通过IDE调试工具来调试找到问题。

关于多线程

我自己直接用的是大锁,也就是在每个函数的第一排加了一行
std::scoped_lock<std::mutex> lock(latch_);

注意如果用这种方法,那么你的函数之间是不能相互调用的,比如你在、FetchPgImp中,准备驱逐一个脏页面,那么你是不能调用FlushPgImp的,否则会引起死锁。同理AllocatePage()前面也是不能加锁的,因为AllocatePage()会被FetchPgImp调用。
如果要写更细粒度的话,可以尝试读写锁分开,然后用Page里面的小锁。不过写的粗糙一点也能过就是了
在这里插入图片描述

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值