Project 1:Buffer Pool

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)auto
  • FetchPgImp(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++。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值