BUFFER POOL
这个项目需要在你的存储管理程序中实现以下三个部分:
项目说明
TASK #1 - LRU REPLACEMENT POLICY
目标
这个组件负责跟踪缓冲池中的页面使用情况。在 src/include/buffer/lru_replacer.h 中实现一个名为 LRUReplacer 的新子类,并在 src/buffer/lru_replacer.cpp 中实现其相应的实现文件。LRUReplacer扩展了抽象的Replacer类(src/include/buffer/replacer.h),它包含了函数规范。
LRUReplacer的最大页数与缓冲池的大小相同,因为它包含BufferPoolManager中所有frame的占位符。然而,在任何给定的时刻,并不是所有的frame都被认为在LRUReplacer中。LRUReplacer被初始化为里面没有frame。然后,只有unpin才会被认为是在LRUReplacer中。
需要实现课堂上讨论的LRU策略,主要实现以下方法。
-
Victim(frame_id_t*) : 删除最近最少被访问的对象,将其内容存储在输出参数中并返回True。如果Replacer是空的,则返回False。
-
Pin(frame_id_t) : 这个方法应该在一个页面被pin之后被调用。它应该从LRUReplacer中移除包含pin的页面的页号。
-
Unpin(frame_id_t) : 当一个页面的pin_count变成0的时候,这个方法应该被调用,这个方法应该把包含unpin的页面添加到LRUReplacer中。
-
Size() : 这个方法返回当前在LRUReplacer中的frame的数量。
实现
这里主要讲思路,主要是课堂上的几个重要概念,frame_id 和 page_id,要吃透。其次是,数据结构的选择,
std::list<frame_id_t> lists_;
std::unordered_map<frame_id_t,std::list<frame_id_t>::iterator> maps_;
,如果只是简单的list,很有可能会timeout,因为你其他部分可能会冗余,所以这里能快就快。
代码检查
提交到Gradescope前,必须检查代码
$ make format
$ make check-lint
$ make check-clang-tidy
TASK #2 - BUFFER POOL MANAGER INSTANCE
目标
接下来,你需要在你的系统中实现缓冲池管理器(BufferPoolManagerInstance)。BufferPoolManagerInstance负责从DiskManager中获取数据库页面并将其存储在内存中。BufferPoolManagerInstance也可以将脏页写入磁盘,当它被明确指示这样做时,或者当它需要驱逐一个页面以腾出空间给新的页面时。
在源文件(src/buffer/buffer_pool_manager_instance.h)中实现头文件(src/include/buffer/buffer_pool_manager_instance.cpp)中定义的下列函数。
-
FetchPgImp(page_id)
-
UnpinPgImp(page_id, is_dirty)
-
FlushPgImp(page_id)
-
NewPgImp(page_id)
-
DeletePgImp(page_id)
-
FlushAllPagesImpl()
DiskManager负责处理数据库内页面的分配和取消分配。它执行页面的读写,在数据库管理系统的背景下提供一个逻辑文件层。
实现
在这里记录我遇到的问题,首先,最基本的代码细节问题,主要是,容易受代码提示影响,比如:看下面的提示第二句话,导致自己的代码内部出现一个无用大循环,耗费将近40s(简直是shit),这里把他贴出来,自行领会。警示⚡⚡⚡
auto BufferPoolManagerInstance::NewPgImp(page_id_t *page_id) -> Page * {
// 0. Make sure you call AllocatePage!
// 1. If all the pages in the buffer pool are pinned, return nullptr.
// 2. Pick a victim page P from either the free list or the replacer. Always pick from the free list first.
// 3. Update P's metadata, zero out memory and add P to the page table.
// 4. Set the page ID output parameter. Return a pointer to P.
std::lock_guard<std::mutex> lock(latch_);
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv//
bool all_pinned = true;
for (size_t i = 0; i < pool_size_; i++) {
auto page = &pages_[i];
if (page->GetPinCount() != 0) {
continue;
}
all_pinned = false;
break;
}
if (all_pinned) {
return nullptr;
}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^//
bool pick_page = false;
frame_id_t frame_id;
if (!free_list_.empty()) {
frame_id = free_list_.front();
free_list_.pop_front();
pick_page = true;
} else {
pick_page = replacer_->Victim(&frame_id);
}
if (!pick_page) {
return nullptr;
}
最后,成绩在10s左右,当然,还可以进行优化,后文在说明几个优化代码的方法。
TASK #3 - PARALLEL BUFFER POOL MANAGER
目标
ParallelBufferPoolManager是一个持有多个BufferPoolManagerInstances的类。对于每一个操作,ParallelBufferPoolManager都会选择一个BufferPoolManagerInstance并委托给该实例。
使用给定的页面ID来决定使用哪个特定的BufferPoolManagerInstance。如果有num_instances个BufferPoolManagerInstances,那么需要一些方法将给定的页面id映射到[0, num_instances]范围内的数字。在这个项目中,将使用mod操作符,page_id mod num_instances将把给定的page_id映射到正确的范围。
当ParallelBufferPoolManager第一次被实例化时,它的起始索引应该是0。每次你创建一个新的页面时,你将尝试每个BufferPoolManagerInstance,从起始索引开始,直到有一个成功。然后将起始索引增加一个。
确保当你创建单个BufferPoolManagerInstances时,你使用接受uint32_t num_instances和uint32_t instance_index的构造函数,以便正确创建页面ID。
你需要在源文件(src/buffer/parallel_buffer_pool_manager.h)中实现头文件(src/include/buffer/parallel_buffer_pool_manager.cpp)中定义的下列函数。
ParallelBufferPoolManager(num_instances, pool_size, disk_manager, log_manager)
~ParallelBufferPoolManager()
GetPoolSize()
GetBufferPoolManager(page_id)
autoFetchPgImp(page_id)
UnpinPgImp(page_id, is_dirty)
FlushPgImp(page_id)
NewPgImp(page_id)
DeletePgImp(page_id)
FlushAllPagesImpl()
实现
这个项目,自己遇到的问题主要是两个兄弟派生类之间是不能直接调用方法的,必须是父类下面的公有成员函数才能使用,所以这里调用公有的回调函数,而回调函数就是把传入的函数指针当作参数使用,不难理解,就是传入一个参数,只是他是函数指针而已,这里我们不用管,因为,只有打分时,才会涉及回调。其次是,遇到的性能瓶颈是,没有按照题目要求,将每次的空位记住,而是每次调用for循环,找到这时候还有位置的pool,虽然也可以,但是会慢一点点。
调试与优化心得
刚开始进行gradescope测试,测试成绩为90。
问题在于:test_memory_safety (main.TestProject1) (0.0/10.0)
Test Failed: False is not true : Timeout Happened during valgrind
解决方法就是优化代码,因为你的代码太慢了,超时了。那么在后面,我利用了代码分析工具——gprof+gprof2dot+dot,这一套工具能够可视化的展现出代码的瓶颈。食用方法
其中,需要针对本项目做的前期准备工作是,加入编译选项-pg和-g
mkdir build_debug
cmake -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_CXX_FLAGS=-pg ..
make -j 4
可以看出,我的newpage实现肯定存在问题,于是便有上文的一段shit code,当然里面还是有一些代码存在优化的地方,比如free_list_当中的元素必定不在pages__当中,为此不必需要erase,徒增复杂度,为此,这里的优化需要考量,其次是empalce_back(替代push_back)的使用,count(替代find)的使用,这里可以参考effective C++。