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

文章目录

    • 前言
    • 题目讲解
      • 属性
      • 方法
        • NewPgImp
        • FetchPgImp
        • UnpinPgImp
        • FlushPgImp
        • FlushAllPgsImp
        • DeletePgImp
    • 代码实现
    • 关于多线程

前言

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

题目讲解

  • 在实现缓冲池之前,我们先需要知道两个概念的区别: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()来调用析构函数。
    CMU 15-445 (FALL 2022) Project #1 Buffer Pool题解_第2张图片

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

关于多线程

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

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

你可能感兴趣的:(C++,#,CMU15-445,数据库,c++)