BUFFER POOL
完全新手向
2023fall
task1 LRU-K replacement policy
- 个人tip:首先可以先看看测试文件怎么写的(记得把DISABLE_去掉),先了解LRU是什么意思,然后再开写,只有一个锁,锁整个结构体所以在每个函数的开头直接用
std::lock_guard<std::mutex> lock(latch_);
- K:k 距离计算为当前时间戳与前 k 次访问的时间戳之间的时间差。如果没访问k次就是+inf,就是优先删除列表小于k的页;
Evict(frame_id_t* frame_id)
:逐出,这个应该是整个最难的,其他都不难:- 循环map找到要删除的页,他的策略是优先
history_.size()<k
,页的编码最小,时间戳的时间最长;每次调用这个函数删除一个页,然后把这个页的id返回给传入参数frame_id;
- 循环map找到要删除的页,他的策略是优先
RecordAccess(frame_id_t frame_id)
:记录在当前时间戳访问给定帧 ID- 涉及到时间戳肯定是时间戳先加1,然后判断在不在map里,然后直接加;这里可以在Node类中写一个加时间戳的函数,逻辑就是**在链表头加一个时间戳,如果链表size>k就pop_back()**这样可以比较容易的访问第k个时间戳;
Remove(frame_id_t frame_id)
:清除与帧关联的所有访问历史记录。- 这个就是在map里erase,重点是要维护curr_size_,不过我没写;
SetEvictable(frame_id_t frame_id, bool set_evictable)
:改变页的状态- 这个就是直接改变;
Size()
:记录有多少个要移除的节点;- 如果维护了curr_size_就直接返回,否则就是遍历一遍map,维护比较好,但测试用例比较少所以无所谓;
task2 Disk Scheduler磁盘调度
make disk_scheduler_test -j$(nproc)
和gdb ./test/disk_scheduler_test
目标:实现DiskScheduler 类,构造函数和析构函数已经写好了,只需要写Schedule(DiskRequest r)
和StartWorkerThread()
;这个task个人觉得也是比较容易;
Channel类提供了一个线程间共享数据的方法;src/include/common/channel.h
这个也不需要加锁因为channel里面维护了;
Schedule(DiskRequest r)
:注意要使用std::move(),我觉得这就是包含一个类型转换的过程,Put函数在channel.h中已经写好了,Get函数也写好了,而且是自己维护的;StartWorkerThread()
:这个直接调用disk_manager_中写好的WritePage和ReadPage;
task3 BUFFER POOL MANAGER
这个有点难哈
首先Page结构存放page的信息很重要!page_这个数组存page
page_table_
:通过page_id_找对应的帧,在这里帧就是bufferpool的格子,格子的大小就是pool_size_;BufferPoolManager是Page的友元可以直接访问私有成员;这里非常注意就是page_table_是page_id是key,frame是value;
pages_ = new Page[pool_size_];
这里的pages_[0]就是第0号page的本体,返回Page*需要&pages_[0]
NewPage(page_id_t* page_id)
:新建一个page,如果free list中有就去一个空的帧,如果没有就拿掉一个帧,用这个拿掉的帧,如果没有可以Evict的,就返回nullptr;
- 第一步取frame_id_,如果有free就从freelist中取,没有就用replacer_移除一个,还是没有就return nullptr
- 用
AllocatePage()
取一个新的页码
auto pa = AllocatePage(); //新建一个page
*page_id = pa;
replacer_->RecordAccess(val); // lru记录frame
replacer_->SetEvictable(val, false);
page_table_[pa] = val;
pages_[val].page_id_ = pa;
pages_[val].pin_count_ = 1;
pages_[val].is_dirty_ = false;
return &pages_[val];
一套流程
2. FetchPage(page_id_t page_id)
:这个和newpage比较相似,我觉得是最难写的
- 如果已经在page_table里面了,就直接取;如果不在就需要,重复一下newpage的流程,先新建一个空的frame,把这个frame对应的所有东西清空,如果是脏页就需要flush;然后把这个page_id的内容写到frame上。
auto promise1 = disk_scheduler_->CreatePromise();
auto future1 = promise1.get_future();
disk_scheduler_->Schedule({/*is_write=*/false, pages_[val].data_, /*page_id=*/page_id, std::move(promise1)});
要记得pages_[val].pin_count_++;
3. UnpinPage(page_id_t page_id, bool is_dirty)
:感觉这个比较容易
- 如果没有这个页就返回flase,如果已经是
pin_count_ == 0
返回false; - 否则就是
pin_count_--
,如果是脏页就改状态,这里有一个问题就是,只需要改脏页情况;
FlushPage(page_id_t page_id)
:disk_scheduler_主要用这个
if (pages_[val].is_dirty_) {
auto promise1 = disk_scheduler_->CreatePromise();
auto future1 = promise1.get_future();
disk_scheduler_->Schedule({/*is_write=*/true, pages_[val].data_, /*page_id=*/page_id, std::move(promise1)});
future1.get();//这个非常重要,如果没有这个就会出错;
pages_[val].is_dirty_ = false;
return true;
}
在本地测试中,由于一开始没加future.get()时而通过时而不通过
这是由于如果不加future.get(),他可能还没返回结果呢,就开始向下执行了;一定要记得加这个取回状态;(这个问题的解决办法是看知乎一位大佬的思路才明白)
5. DeletePage(page_id_t page_id)
:前面加一些题目里写的判断条件
page_table_.erase(page_id);
pages_[val].page_id_ = INVALID_PAGE_ID;
pages_[val].pin_count_ = 0;
pages_[val].is_dirty_ = false;
free_list_.push_back(val);
replacer_->Remove(val);
DeallocatePage(page_id);
return true;
FlushAllPages()
:遍历一遍map调用flushpage;