CMU-15445 2021 Project 1-BUFFER POOL (缓存池)

CMU-15445 2021 Project 1-BUFFER POOL (缓存池)

先贴结果图:
在这里插入图片描述

CMU禁止公开源代码哦~, 有问题欢迎私聊, 评论或者加群: 484589324交流~

Project1的内容是实现一个缓存池, 因为实验数据库是面向磁盘的数据库(这样才能提供海量存储), 而内存是很小的, 因此我们需要不断的在内存和磁盘中交换内容才能实现功能

一共有三个子实验, 难度大概是: 2 > 1 > 3

最核心的概念有:

  1. page(物理页), 数据库存储的原子(最小)单位, 也是磁盘和内存交换的最小单元, 磁盘上有海量的page, 由page_id唯一标识
  2. frame(内存帧), 帧和页的大小一致, 可以将内存看做一个由桢组成的数组, 实验中由pages_[frame_id].GetData()就可以获取到一个桢的地址, 帧的数量有限(内存很小), 由frame_id唯一标识, 一个帧在生命周期中会不断的被清空, 被写入别的page, 被刷盘等等
  3. pin_count(某个帧被多少个线程引用), 如果不为0, 则它一定不能在LRU中, 如果变为0了, 就将它存入LRU

1. LRU REPLACEMENT POLICY (LRU淘汰策略)

内存很小, 磁盘很大, 因此需要不断的去淘汰内存中的frame以写入磁盘中的page, 而在诸多淘汰算法中, 最经典的一定是最近最少使用(LRU)算法, 因为这种算法不会轻易淘汰热点数据, 客观来说比较合理

LRU算法也是面试中常考的内容, 建议在做实验之前先做一下这道题LeetCode146.LRU

做完之后, 相信对LRU基本的认知你已经拥有了, 接下来开始实验:

1-1. 讲解一下需求

需要实现的函数不多, 需求如下:

  • bool Victim(frame_id_t* f_id) : 强制淘汰一帧, 将被淘汰的帧的id存入*f_id, 如果LRU为空无法淘汰返回false(这种情况不要更改 *f_id), 不为空淘汰成功了就返回true
  • void Pin(frame_id_t f_id) : 将某个帧固定在内存中, 不允许LRU淘汰它(也就是直接从LRU删除该帧) , 当然如果找不到该帧就不用删除了(也没法删除)
  • void Unpin(frame_id_t) : 某个帧没有线程引用了, 可以存入LRU等待被淘汰了
  • Size() : LRU中的帧数, 顾名思义即可

1-2. 注意点

  • 实现思路和LeetCode一样, 双链表 + 哈希即可, 注意并发安全
  • 注意Unpin的时候如果该frame已经在内存中了, 函数直接返回而不是将该frame更新到头部, 这一点和LeetCode不一样(比LeetCode更简单), 仔细想一下这样是合理的, 因为LRU中U的含义是使用, 只是Unpin并没有使用, 所以不应该对LRU的结构有影响

2. BUFFER POOL MANAGER INSTANCE (缓存池管理者实例)

实验1中最难的一项(也没有很难), 利用自己的LRU, 实际的去管理内存中的帧

2-1. 讲解一下需求

每个函数之间非常独立, 逐一击破即可:

  • Page* FetchPgImp(page_id) : 从内存中抓取一页, 记得pin_count从0变1的时候要从LRU删除(调用pin)
  • bool UnpinPgImp(page_id, is_dirty) : 一个线程不再引用一个页了, 将其pin_count减少1, 并通知manager是否该页已经脏了, 有任何异常情况(例如pin_count已经为0了)就返回false, 记得pin_count变为0时调用LRU的unpin
  • bool FlushPgImp(page_id) : 将某一页刷盘, 无论是否dirty, 这是强制刷盘
  • Page* NewPgImp(page_id) : 不是通过磁盘刷进内存, 而是直接在内存中新建一页, 注意新页要第一时间写入到磁盘(尽管没什么内容), 以确保磁盘能感知到该页, 否则被LRU淘汰就完蛋了, 如果内存全是被pin的页就没办法了, 返回空指针
  • bool DeletePgImp(page_id) : 从磁盘中删除一页, 当然也要在内存中删除它了, 但是如果pin_count非0, 就不能删除, 而是返回false
  • void FlushAllPagesImpl() : 最好欺负的一个, 挨个刷盘就行了

2-2. 注意点

  • 挨个函数去实现即可, 多看Teacher的注释, Buffer Pool的函数之间不存在什么调用关系, 每一个功能都是独立的

  • 注意并发安全, 锁的粒度可以很粗

  • 实现完一个函数之后可以看看和我上面需求中的点是否比较符合了, 可能能避免一些坑

3. PARALLEL BUFFER POOL MANAGER (并行的缓存池管理者)

为了避免频繁争抢锁, 一个不错的idea是做多个BPM(Buffer Pool Manager), 根据page_id决定由哪个manager来管理, 这样抢锁的程度会低很多

实验中用的是简单的取模运算, 不是什么复杂的哈希算法, 即根据page_id取模bpm的数量来判断要分配到哪个bpm

3-1. 讲解一下需求 (忽略构造析构函数)

很简单, 如下:

  • GetPoolSize() : 每个bpm的容量 * bpm的个数
  • GetBufferPoolManager(page_id) : 取模即可, 一行结束
  • FetchPgImp(page_id) : 一行
  • UnpinPgImp(page_id, is_dirty) : 一行
  • FlushPgImp(page_id) : 一行
  • Page* NewPgImp(page_id) : 从start_idx(类构造时初始为0)开始转一圈(转回到start_idx), 看看哪个bpm能New成功, 转了一圈都不行就返回空指针, 记得无论结果如果都将start_idx增加1并取模size, 记得加锁
  • DeletePgImp(page_id) : 一行
  • FlushAllPagesImpl() : 一个循环, 两三行

3-2. 注意点

  • start_idx会有并发问题, 应该是需要加个锁, 虽然数据好像比较弱不加也能过

  • 调用自己之前的Buffer Pool即可, 这个真的不难

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值