【CMU 15-445】Proj1 Buffer Pool Manager

Buffer Pool Manager

  • 通关记录
  • Task1 LRU-K Replacement Policy
  • Task2 Disk Scheduler
  • Task3 Buffer Pool Manager
    • FlushPage
    • FlushAllPages
    • UnpinPage
    • NewPage
    • FetchPage
    • DeletePage
  • Optimizations

CMU-15445汇总
本文对应的project版本为CMU-Fall-2023的project1
由于Andy要求,本博客只提供思路,不会公开任何代码

通关记录

【CMU 15-445】Proj1 Buffer Pool Manager_第1张图片

【CMU 15-445】Proj1 Buffer Pool Manager_第2张图片
目前的rank还比较低,lru-k后续会优化下

Task1 LRU-K Replacement Policy

LRU-KLRU一样,都属于缓冲区的页面置换算法,不同的是,LRU-K考虑的是页面的之前第K次访问时间戳与当前时间戳的差(若不足K次则优先驱逐,若有多个访问不足K次的页面,则按LRU规则驱逐),而LRU考虑的是页面最近一次访问时间戳与当前时间戳的差。一个具体的例子如下图所示,假设K=3buffer大小为3,访问序列为1 2 3 2 3 2 3 1 1 4,当访问4时,会将1驱逐掉,图中当页面访问不足K次时,时间戳为最新访问时间戳,否则为前第K次的时间戳(蓝色数字为时间戳)。
【CMU 15-445】Proj1 Buffer Pool Manager_第3张图片
Task1的要求是实现src/buffer/lru_k_replacer.cpp文件中的如下几个函数:

  • Evict(frame_id_t* frame_id):使用LRU-K算法驱逐一个frame,并使用参数返回frame_id;返回值类型为bool,若驱逐成功返回true,若当前可驱逐frame数量为0,则返回false
  • RecordAccess(frame_id_t frame_id):记录给定frame的访问历史
  • Remove(frame_id_t frame_id):将给定framebuffer中移除
  • SetEvictable(frame_id_t frame_id, bool set_evictable):设置frame可驱逐不可驱逐,若设置前后该属性不一致,则需要修改类内维护的可驱逐frame数量属性
  • Size():返回当前可驱逐frame数量

个人建议的实现顺序为Size->SetEvictable->RecordAccess->Evict->Remove
主要的难点在于RecordAccessEvict中各frameLRU-K信息维护与驱逐算法实现,目前我实现的版本为暴力版本,即在RecordAccess为每个frame维护一个大小为K的访问时间戳队列;在Evict中,遍历所有可驱逐的frame,找出LRU-K timestamp最小的那个,将其驱逐。(这个做法明显太慢了,后续优化一下)

Task2 Disk Scheduler

此部分需要实现一个简单的磁盘调度器,接收BufferPoolManager发来的读写磁盘请求放入一个请求队列中;然后启动一个新线程,不断从请求队列中获取请求,根据请求类型调用对应DiskManager的读写函数进行磁盘读写。主要实现文件src/storage/disk/disk_scheduler.cpp中的两个函数:

  • Schedule(DiskRequest r):接收请求并放入请求队列
  • StartWorkerThread():线程函数,从请求队列中获取新请求,并根据请求类型调用磁盘读写函数

个人建议的实现顺序为Schedule->StartWorkerThread
不需要考虑队列的线程安全性,已经有一个Channel类帮忙实现了生产者消费者模型;关于std::promise的用法,可参考disk_scheduler_test.cpp文件。

Task3 Buffer Pool Manager

这一部分需要实现一个BufferPoolManager,结合前两部分实现的LRU-K ReplacerDisk Scheduler进行缓冲区的管理物理页的开辟、获取与释放BufferPoolManager类维护的数据结构中包含一个Page类的数组(在BufferPoolManager的构造函数中会开辟空间),数组中的每个元素即为一个一个的framePage类主要包含以下几个成员:

  • page_id_t page_id_:表示该frame指向的物理页面id
  • char* data_:用于储存对应物理页面中的真实数据
  • int pin_count_:故名思意,该framepin count,表示被多少个进程pin住,当pin_count_不为0时,该frame不能被驱逐
  • bool is_dirty_:该frame是否被写过,如果被写过则需要将内容写回磁盘

在这个任务中,我们需要实现文件src/buffer/buffer_pool_manager.cpp中的以下方法:

  • FetchPage(page_id_t page_id)
  • UnpinPage(page_id_t page_id, bool is_dirty)
  • FlushPage(page_id_t page_id)
  • NewPage(page_id_t* page_id)
  • DeletePage(page_id_t page_id)
  • FlushAllPages()

个人建议的实现顺序为:FlushPage->FlushAllPages->UnpinPage->NewPage->FetchPage->DeletePage
接下来我介绍下我每个函数的实现思路

FlushPage

功能:将给定的缓存页写回磁盘,无视is_dirty_标志
参数:page_id表示给定的想要flush到磁盘的物理页id
返回值:bool,若page_id不存在,返回false;否则true
实现:BufferPoolManager类中需要维护一个page_table_,存放着每个已分配物理页对应的frame,通过page_table_查找到给定page_id对应的frame_id,然后调用Disk Scheduler提供的scheduler方法发出写请求即可,最后清空frameis_dirty_标志。

FlushAllPages

功能:将所有有效的缓存页写回磁盘,无视is_dirty_标志
参数:无
返回值:void
实现:遍历整个buffer的所有frame,若frame上的page_id不为空,则使用与FlushPage类似方法写回磁盘即可。

UnpinPage

功能:将指定的物理页对应的framepin_count减1
参数:page_id表示给定的物理页id,is_dirty表示在pin住frame时是否发生了写操作
返回值:bool,若指定的物理页id不存在,返回false;否则true
实现:首先根据page_id找到对应的frame,将framepin_count减1,此时若pin_count为0,还需调用LRU-K Replacer提供的SetEvictable函数设置frame的状态为可驱逐。然后,根据is_dirty参数设置frameis_dirty_成员即可。

NewPage

功能:找到一个空闲frame新分配一个物理页,并将该物理页的内容读取到刚找到的这个frame
参数:page_id_t *page_id分配的物理页id以参数形式返回
返回值:Page*表示新分配的物理页最终存放的frame地址
实现:找到空闲的frame首先从一个free list里面找,如果free list为空表明现在的buffer已满,需要使用LRU-K Replacer驱逐一个frame并将其作为新物理页的承载体(若该frameis_dirty_true,那么需要先将frame中的内容写回对应的物理页)。接着,调用AllocatePage函数分配新物理页,并设置frame中的相关成员即可。最后,需要调用LRU-K Replacer提供的RecordAccess函数记录下访问历史,并调用SetEvictable函数pin住frame

FetchPage

功能:给定物理页id,获取该物理页所对应的frame
参数:page_id表示给定的物理页id
返回值:Page*表示给定物理页所在的frame地址
实现:首先从page_table_中寻找给定物理页对应的frame,若未找到,则需要驱逐缓存页,这一块的处理与NewPage函数中驱逐的处理一致,代码可以复用;若找到则进行下一步。然后,将最终确定的frame记录访问历史pin住操作,也与NewPage中的操作一致。

DeletePage

功能:给定物理页id,将物理页对应的framebuffer中删除
参数:page_id表示给定的物理页id
返回值:bool,物理页不在buffer中或删除成功则返回true;物理页存在且对应的frame仍然被pin住(pin_count不为0)时,则返回false
实现:查找物理页对应的frame,将page_idpage_table_中移除,调用LRU-K ReplacerRemove函数将frame_idbuffer中移除,并将frame_id加入free_list_中,重置frame的成员,最后调用DeallocatePage将物理页回收

Optimizations

你可能感兴趣的:(CMU,15-445,数据库,database,c++)