P1 - Buffer Pool -- Task #2 - LRU-K Replacement Policy

The LRU-K algorithm evicts a frame whose backward k-distance is maximum of all frames in the replacer. Backward k-distance is computed as the difference in time between current timestamp and the timestamp of kth previous access. A frame with less than k historical accesses is given +inf as its backward k-distance. When multipe frames have +inf backward k-distance, the replacer evicts the frame with the earliest timestamp.

这里有几个关键点

1、这个 LRU-K 在换出时是根据什么来对候选项进行选择/排序的?

根据当前访问时间和每一项的前第 k 次时间的差

2、如果有候选项以前没有被访问过 k 次怎么办?

距离加上正无穷,即优先换出

3、如果有多个候选项以前没有被访问过 k 次怎么办?

选择最早被访问的

The maximum size for the LRUKReplacer is the same as the size of the buffer pool since it contains placeholders for all of the frames in the BufferPoolManager. However, at any given moment not all the frames in the replacer are considered to be evictable. The size of LRUKReplacer is represented by the number of evictable frames. The LRUKReplacer is initialized to have no frames in it. Then, only when a frame is marked as evictable, replacer's size will increase.

1、LRUKReplacer 的 max_size 和 buffer pool 的 size 有什么关系?代表什么?

LRUKReplacer 的 max_size 和 buffer pool 的 size 相等,代表 buffer pool 中最多可以容纳的 page

2、LRUKReplacer 的 size 又代表什么?

代表可以被换出的 frame 数量

3、初始时 size 为多少?意味着什么?

size 为 0,意味着均不可被换出,需要注意不可被换出不等于 LRUKReplacer 没有空闲

4、如果预先给 LRUKReplacer 的每一项分配空间,每一项有几种状态?

3 种,空 / 可被换出 / 不可被换出

5、3 种状态如何区别?

使用一个二维数组记录时间,有 K 行,max_size 列,默认为 0,记录访问时间

一个一维 bool 数组记录是否可以被换出,默认为 false

先判断时间是否为 0 得到是否为空,再根据 bool 数组判断是否可以被换出

Evict(frame_id_t*) : Evict the frame with largest backward k-distance compared to all other evictable frames being tracked by the Replacer. Store the frame id in the output parameter and return True. If there are no evictable frames return False.

空的 frame 可以被换出吗?怎么判断?

空的 frame 不可以被换出!事实上在 part3 中可以知道,空的 frame 在外层直接加入了 freelist

将空的 frame 的 evictable 属性赋值为 false,这样在 Evict(frame_id_t *frame_id) 函数中就不会当作候选

项目结构如下:

成员变量:

size_t curr_size_;      // cur_size 表示可以被换出的 page
size_t replacer_size_;
size_t k_;
std::mutex latch_;
size_t **time_;         //二维数组记录时间
bool *evictable_;       //是否可以被换出

成员函数:

//构造函数
explicit LRUKReplacer(size_t num_frames, size_t k);
/*
初始化成员变量,注意 new 的对象不一定会有初始值,需要手动遍历一遍赋初始值
*/

//析构函数
~LRUKReplacer();
/*
对数组使用 delete [],注意二维数组先要从内部 delete 一遍
*/

//选择一个换出,将 frame_id 赋值给指针类型参数,没有可被换出的返回 false
auto Evict(frame_id_t *frame_id) -> bool;
/*
换出成功后要进行初始化 time,同时减小 size(可被换出的数量减少了)
换出的 frame 必须满足可以被换出的前提条件,即 evictable 对应为 true

当所有 frame 都访问满 k 次时选择前第 k 次最小,即 time 数组第一行中最小的那一列
这样同时满足了当有 frame 访问未满 k 次时优先选择那一列,此时 time 数组第一行中最小为 0
当有多个 frame 访问未满 k 次时,选择最早的那一个,需要从上往下遍历这些符合条件的每一列
*/

//访问对应的 frame
void RecordAccess(frame_id_t frame_id);
/*
对于第一次访问的 frame,即 time 数组对应项为 0 的初始化 evictable 为 false
做遍历让 time 对应那一列往上移动一格即可
*/

//设置指定 frame 的 evictable 属性
void SetEvictable(frame_id_t frame_id, bool set_evictable);
/*
记得从 false 到 true 要 curr_size_++,true 到 false 要 --
*/

//删除指定的可被换出的 frame 的所有数据
void Remove(frame_id_t frame_id);
/*
指定的 frame 必须是可以被换出的,即先要判断 evictable
删除需要初始化 time 数组对应项为 0,以此来指明该 frame 已为空
*/

下面是一些坑:

1、如何记录访问时间?

要精确到微秒!如果只精确到毫秒,在单元测试中有些挨着近语句调用 RecordAccess() 记录的时间是一样的,会使判断换出的比较逻辑出问题。

auto GetTime() {
    auto *tv = new timeval;
    gettimeofday(tv, nullptr);
    size_t t = tv->tv_sec * 1000000 + tv->tv_usec;
    delete tv;
    return t;
}

2、在 SetEvictable(frame_id_t frame_id, bool set_evictable) 中有这样一句话:

For other scenarios, this function should terminate without modifying anything.

这个 other scenarios 指的是什么?

我们在这个函数中根据情况直接设置了对应 frame 的 evictable 属性并对 size 做了增减,但是如果 SetEvictable(frame_id_t frame_id, bool set_evictable) 参数 frame_id 对应的 frame 是一个空的 frame 呢?

在 evictable 数组中我们不能直接判断对应 frame 是否为空,只根据某一项的 true / false 和参数 set_evictable 作比较然后增减 size,会有 2 个问题:

#1、如果直接设置了 evictable 属性,就可能出现空的 frame 对应 evictable 为 true 的情况。这样在 Evict(frame_id_t *frame_id) 函数中可能误判该 frame 可被换出得到错误结果 

#2、使得 size 出现错误。因为空 frame 无论怎么设置 evictable 属性都不应该影响 size。

所以比较时必须先判断是否为空,即 time 数组对应项是否为 0,然后再设置 evictable 属性并修改 size。

下面给出我的 Evict() 代码:

auto LRUKReplacer::Evict(frame_id_t *frame_id) -> bool {
  auto current_timestamp = GetTime();
  size_t max = 0;
  size_t p = 0;
  bool flag = false;  // 是否有没达到 K 次的 frame
  std::scoped_lock<std::mutex> lock(latch_);
  for (size_t i = 0; i < replacer_size_; i++) {
    if (evictable_[i]) {
      if (time_[0][i] == 0) {              // 次数未达到K
        for (size_t j = 1; j < k_; j++) {  // 第一次进入循环时,flag 为 false,不用比较 max
          if (time_[j][i] != 0 &&
              (!flag || current_timestamp - time_[j][i] > max)) {  // 当前时间戳向前k次相差越大越不活跃
            max = current_timestamp - time_[j][i];
            p = i;
            flag = true;
            break;
          }
        }
      } else {
        if (!flag && current_timestamp - time_[0][i] > max) {  // 间隔时间更长
          p = i;
          max = current_timestamp - time_[0][i];
        }
      }
    }
  }
  if (max != 0) {
    *frame_id = p;
    curr_size_--;                      // 换入新页面,空闲减少,默认不可被换出
    evictable_[p] = false;             // evict 函数要根据 evictable 判断是否可以换出
    for (size_t i = 0; i < k_; i++) {  // 将历史访问记录初始化
      time_[i][p] = 0;
    }
    return true;
  }
  return false;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值