CMU15445(2023 Spring) - Project 1. Buffer Pool

文章讨论了LRU-K替换策略,它是LRU的优化版,避免了短暂未访问页面被过早淘汰的问题。LRU-K基于k-distance概念,维护访问次数小于k的FIFO队列和达到k的LRU队列。实现中使用链表和红黑树,并关注线程安全。此外,文章还提到了BufferPoolManager在页管理中的作用以及Read/WritePageGuards的引入,用于管理读写保护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列笔记

环境配置
Project 0. C++ Primer
Project 1. Buffer Pool
Project 2. B+Tree

作业链接

作业链接
本次project分为三个部分:LRU-k、缓冲池、读写锁,感觉跟往年的大差不差

Task 1 LRU-K Replacement Policy

LRU应该都比较熟悉,可以参照LeetCode 146,也就是当cache装满后,踢掉最早访问的那个页面(frame),但是朴素的LRU算法有很明显的缺点:一个我频繁访问的页面刚好有一小段时间没访问,导致被一个只访问过一次的页面给踢出去了,这明显是不合理的。而LRU-k则是它的变种,它意味着优先删除的是没有被访问到k次的页面,这样就可以避免上面那种情况的发生。

这里定义一个所谓的k-distance:先看原文:Backward k-distance is computed as the difference in time between current timestamp and the timestamp of kth previous access. A frame with fewer than k historical accesses is given +inf as its backward k-distance. 可以知道,首先我们根据当前时间now减去页面的最早访问时间,就可以得到页面的k-distance,其中当页面访问次数小于k时,这个值需要加上一个+inf。形式上,我们可以得到:
D ( f r a m e ) = { n o w − l a s t T i m e ( f r a m e ) cnt(frame) >= k + i n f cnt(frame) < k D(frame)= \begin{cases} now - lastTime(frame)& \text{cnt(frame) >= k}\\ +inf& \text{cnt(frame) < k} \end{cases} D(frame)={nowlastTime(frame)+infcnt(frame) >= kcnt(frame) < k
然后是删除策略,删除策略就是删除k-distance最大的那个值,+inf自然大于别的值,因此我们会优先删除访问次数小于k的页面,如果有多个访问次数小于k的页面怎么办呢?原文是这么写的:When multiple frames have +inf backward k-distance, the replacer evicts the frame with the earliest overall timestamp (i.e., the frame whose least-recent recorded access is the overall least recent access, overall, out of all frames). 也就是说,当多个页面的k-distance值为inf时,采用FIFO策略驱逐。当没有inf时,我们知道当前时间now固定,因此k-distance的大小与页面最早访问时间呈负相关,因此我们只需要删除最早访问时间最小的一个页面即可,也就是说此时删除策略退化成了LRU。当然,同时还应当注意页面的is_evictable_权限是否为true。

搞清楚了需求,就要想一下实现了,具体实现其实一般都是维护两个队列(链表),一个装访问k次以下的页面,为FIFO队列,一个装达到k次的页面,也就是LRU队列。其中FIFO队列没什么好说的,链表模拟即可,需要注意的是此处的LRU队列的实现,和上面给出Leetcode题目、也就是传统LRU、或者说LRU-1有所区别,LRU-1保证了每次更新页面后页面都必然会出现在链表的尾部,而LRU-K则需要根据最早访问时间的次序确定页面更新后的优先级。涉及到频繁排序,我们自然想到可以采用红黑树去模拟这个LRU队列,具体而言,我们需要维护一个装有Node指针的std::set。实现上,可以维护一个存储frame_id_t -> shared_ptrunordered_map以实现内存管理,而对于两个队列(不是指queue哈),则存储weak_ptr

搞清楚这些,剩下的内容就很简单了,至于线程安全,我们只需要全程加锁即可(话说应该是可以实现无锁的吧,,,不过我是懒狗,直接加大锁,也没有细粒度加锁~)

Task 2 - Buffer Pool Manager

这一部分比较简单,所谓的Buffer Pool其实就是个Page进进出出的管理器,跟着Tips走就行了,需要注意的是这里要用到前面写好的LRU-K,意味着前面实现的效率是和后面环环相扣的。。此外还要注意线程安全。

Task 3 - Read/Write Page Guards

这一个Task以前的Lab是没有的,是23Spring为了后面的B+树开发方便而新增的,整体也不难,就是利用RAII机制去管理读写保护,,,

<think>嗯,用户想在Bustub项目中实现Project 0和Project 1的功能,需要根据课程要求修改源码。首先,我得回想一下Bustub的结构。Project 0通常是关于缓冲池管理器的实现,比如LRU-K替换策略。而Project 1可能涉及B+树索引的实现。用户需要具体的代码修改位置,所以得明确每个项目涉及的模块。 对于Project 0,缓冲池管理器在src/include/buffer目录下。可能需要修改lru_k_replacer.h和lru_k_replacer.cpp,实现Evict函数和RecordAccess方法。另外,buffer_pool_manager_instance.cpp中的FetchPage和UnpinPage方法也需要调整,以使用LRU-K策略。需要检查现有的代码结构,确定需要添加或修改的部分。例如,Evict函数需要找到最不适合保留的帧,可能需要遍历所有frame,比较它们的访问时间差。 Project 1可能涉及B+树的实现,代码可能在src/include/storage/index目录。需要实现插入、分裂和查找操作。比如,在b_plus_tree.cpp中,Insert函数需要处理节点分裂的情况,同时维护父节点的指针。还要考虑并发控制,比如锁的管理。这里可能需要参考课程提供的文档,确保正确实现分裂和合并的逻辑。 用户提到要根据给定的要求链接,所以需要提醒用户具体查看作业文档中的细节。比如,LRU-K中的K值可能是2,需要特别处理历史访问记录。在代码中,可能需要维护两个队列:一个记录最近K次访问的时间戳,另一个用于替换判断。当页面被访问时,更新对应的时间戳队列,如果未满K次,则不能成为候选被替换的页面。 在实现过程中,要注意测试用例。比如,缓冲池管理器的测试可能在test/buffer目录下,确保Evict函数正确移除最旧的页面。而B+树的测试可能涉及多个插入和查询操作,验证树的结构是否正确。 可能遇到的问题包括理解现有的代码框架,比如BufferPoolManager如何与Replacer交互,或者B+树节点如何序列化和反序列化到磁盘。需要仔细阅读现有代码的注释和结构,确保新代码与现有逻辑兼容。例如,在B+树插入时,如果根节点已满,需要创建新的根节点,并更新相关子节点的父指针。 此外,代码风格和规范也很重要,比如遵循项目的命名约定,添加必要的注释,确保内存管理正确,没有泄漏。对于C++项目,智能指针的使用可能需要特别注意,或者在需要的地方正确释放资源。 最后,用户可能需要参考课程提供的资源,比如讲义或示例代码,确保实现符合要求。例如,Project 0可能要求特定的API接口,而Project 1可能有关于B+树节点格式的具体规定。需要逐一检查这些要求,并在代码中正确实现。</think>### Bustub项目Project 0和Project 1的修改指南 #### 一、Project 0: Buffer Pool Manager 1. **核心修改文件**: - `src/include/buffer/lru_k_replacer.h` - `src/buffer/lru_k_replacer.cpp` - `src/buffer/buffer_pool_manager_instance.cpp` 2. **具体改动**: - **LRU-K替换策略**: ```cpp // 在lru_k_replacer.cpp中实现Evict逻辑 bool LRUKReplacer::Evict(frame_id_t *frame_id) { // 遍历所有frame,找到k backward distance最大的候选 // 若存在+inf(访问次数未达k次),优先选择最早访问的frame // 更新curr_size_和访问记录 } ``` 需维护两个队列:历史访问队列(未满k次)和缓存队列(已满k次)[^2] - **缓冲池交互逻辑**: ```cpp // 在buffer_pool_manager_instance.cpp中 Page *BufferPoolManagerInstance::FetchPgImp(page_id_t page_id) { // 调用lru_replacer_->RecordAccess更新访问记录 // 若页面未加载,触发Evict并替换 } ``` #### 二、Project 1: B+Tree Index 1. **核心修改文件**: - `src/include/storage/index/b_plus_tree.h` - `src/storage/index/b_plus_tree.cpp` - `src/storage/page/b_plus_tree_page.cpp` 2. **具体改动**: - **插入操作**: ```cpp // 在b_plus_tree.cpp中处理节点分裂 void BPlusTree::Insert(const KeyType &key, const ValueType &value) { // 定位叶子节点后,若节点已满则分裂 // 更新父节点指针并递归处理 } ``` 需特别注意`Split()`方法中键值分配和子节点指针调整 - **并发控制**: ```cpp // 在操作前后添加锁管理 void BPlusTree::StartTransaction(Transaction *transaction) { root_page_id_latch_.WLock(); } ``` #### 三、验证与测试 1. **通过Gradescope测试**: - Project 0需通过`BufferPoolManagerInstanceTest` - Project 1需通过`BPlusTreeConcurrentTest` #### 四、代码标注建议 1. 使用`// TODO(P0):`和`// TODO(P1):`注释标记修改位置 2. 在提交时添加commit message说明: ``` feat(p0): Implement LRU-K eviction policy fix(p1): Handle duplicate keys in B+Tree insertion ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值