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.
大意是,每次替换会优先替换k-距离最远的一个数。
假如这个k是3,简单画一下一个实例,依次加入下面几个数
如果一个数出现次数达到了K次,那么k-distance就是倒数第K次出现的位置
如果一个数出现不到K次,那么k-distance就是+inf
优先驱逐距离为 + i n f +inf +inf的frame,如果有多个这样的数,其实LRU-K是有多种策略来决定下一个驱逐谁的(比如用LRU),在本题目中
When multipe frames have +inf backward k-distance, the replacer evicts the frame with the earliest timestamp.
使用的先进先出(FIFO)策略,比如在上图中,4和1都出现了不足K次,如果要驱逐就驱逐先出现的4。
如果没有k-distance为正无穷的frame,优先驱逐该距离最大的。
我们需要理解为什么需要用LRU-K而非LRU策略
Intra
表达在一个Process里面,Inter
表示在两个Process之间。这个例子是说:一个事务访问一个页,然后另外一个不同流程的事务也访问这个页。这两个事务之前是完全独立的,目的也不一样。一次关联访问的结束
和下一段关联访问开始
之间的时间间隔。HIST(p)
,是关于p的访问记录(history)的。HIST(p,n)
就是页面p倒数第n次被访问的时间。例如HIST(2,1)
就是2号页面最后一次被访问的时间(这个是不算关联访问的,也就是说每段关联访问只被记录一次)LAST(p)
就是页面p最后一次被访问的时间,这个是无论关联访问的LAST(p)
就好了HIST
不同的page出现时间,然后用了循环来寻找满足条件最小的时间戳。如果被驱逐的是个被修改过的页面,还需要将它写回硬盘。 size_t current_timestamp_{0};
size_t curr_size_{0};
size_t max_size_;
size_t replacer_size_;
size_t k_;
std::mutex latch_;
using timestamp = std::list<size_t>;//记录单个页时间戳的列表
using k_time = std::pair<frame_id_t,size_t>;
std::unordered_map<frame_id_t,timestamp> hist;//用于记录所有页的时间戳
std::unordered_map<frame_id_t,size_t> recorded_cnt_;//用于记录,访问了多少次
std::unordered_map<frame_id_t,bool> evictable_;//用于记录是否可以被驱逐
std::list<frame_id_t> new_frame_;//用于记录不满k次的页
std::unordered_map<frame_id_t,std::list<frame_id_t>::iterator> new_locate_;
std::list<k_time> cache_frame_;//用于记录到达k次的页
std::unordered_map<frame_id_t,std::list<k_time>::iterator> cache_locate_;
名称 | 作用 |
---|---|
current_timestamp_ | 当前的时间戳,每进行一次record操作加一 |
curr_size_ | 当前存放的可驱逐页面数量 |
max_size | 最多可驱逐页面数量(去掉被pin住的页面) |
replacer_size_ | 整个主存大小(包括被pin的页面) |
k_ | lru-k中的k值 |
latch_ | 用于维护多线程的锁 |
timestamp | 单个页面的一连串时间戳 |
k_time | 页面和kth时间戳 |
hist | 所有页面的访问记录 |
recorded_cnt_ | 被访问次数的记录 |
evictable | 记录一个页面是否可以被驱逐 |
new_frame_ | 记录不满足k次访问页的页号 |
new_locate_ | 页号到上面这个链表迭代器的哈希表 |
cache_frame | 到达k次页的链表 |
cache_locate_ | 哈希表,解释同上 |
reverse_iterator
反向迭代器从后往前遍历,查找允许被驱逐的页面using k_time = std::pair;
这个表示页面对应的倒数第k次访问的时间戳。std::list<k_time> cache_frame_;//用于记录到达k次的页
std::unordered_map<frame_id_t,std::list<k_time>::iterator> cache_locate_;
cache_frame_
的稳定性 auto kth_time = hist[frame_id].front();//获取当前页面的倒数第k次出现的时间
k_time new_cache(frame_id,kth_time);
auto it = std::upper_bound(cache_frame_.begin(),cache_frame_.end(),new_cache, CmpTimestamp);//找到该插入的位置
it = cache_frame_.insert(it,new_cache);
cache_locate_[frame_id] = it;
方法原型 | 作用 |
---|---|
LRUKReplacer(size_t num_frames, size_t k) | 生成器,num_frames是最大缓存,k是lru_k中的k值 |
auto Evict(frame_id_t *frame_id) -> bool | 驱逐一个页面,并保存到frame_id中 |
void RecordAccess(frame_id_t frame_id); | 增加一个页面的访问记录 |
void SetEvictable(frame_id_t frame_id, bool set_evictable); | 设置一个页面是否可以被驱逐 |
void Remove(frame_id_t frame_id); | 移除指定页面 |
auto Size() -> size_t; | 返回可驱逐页面的大小 |
LRU-K.h
//
// Created by Anti on 2022/12/27.
//
#ifndef LRU_K_H
#define LRU_K_H
#include
#include
#include
#include
#include
#include
class LRUKReplacer {
public:
using frame_id_t = int;
explicit LRUKReplacer(size_t num_frames, size_t k);
~LRUKReplacer()=default;
auto Evict(frame_id_t *frame_id) -> bool;
void RecordAccess(frame_id_t frame_id);
void SetEvictable(frame_id_t frame_id, bool set_evictable);
void Remove(frame_id_t frame_id);
auto Size() -> size_t;
private:
size_t current_timestamp_{0};
size_t curr_size_{0};
size_t max_size_;
size_t replacer_size_;
size_t k_;
std::mutex latch_;
using timestamp = std::list<size_t>;//记录单个页时间戳的列表
using k_time = std::pair<frame_id_t,size_t>;
std::unordered_map<frame_id_t,timestamp> hist;//用于记录所有页的时间戳
std::unordered_map<frame_id_t,size_t> recorded_cnt_;//用于记录,访问了多少次
std::unordered_map<frame_id_t,bool> evictable_;//用于记录是否可以被驱逐
std::list<frame_id_t> new_frame_;//用于记录不满k次的页
std::unordered_map<frame_id_t,std::list<frame_id_t>::iterator> new_locate_;
std::list<k_time> cache_frame_;//用于记录到达k次的页
std::unordered_map<frame_id_t,std::list<k_time>::iterator> cache_locate_;
static auto CmpTimestamp(const k_time &f1,const k_time &f2) -> bool;
};
#endif //LRU_K_H
LRU-K.cpp
//
// Created by Anti on 2022/12/27.
//
#include "LRU_K.h"
LRUKReplacer::LRUKReplacer(size_t num_frames, size_t k) : replacer_size_(num_frames), k_(k) {
max_size_=num_frames;
}
auto LRUKReplacer::Evict(frame_id_t *frame_id) -> bool {
std::lock_guard<std::mutex> lock(latch_);
/**
* 如果没有可以驱逐元素
*/
if(Size()==0)
{
return false;
}
/**
* 首先尝试删除距离为无限大的缓存
*/
for(auto it = new_frame_.rbegin();it!=new_frame_.rend();it++)
{
auto frame = *it;
if(evictable_[frame])//如果可以被删除
{
recorded_cnt_[frame] = 0;
new_locate_.erase(frame);
new_frame_.remove(frame);
*frame_id = frame;
curr_size_--;
hist[frame].clear();
return true;
}
}
/**
* 再尝试删除已经访问过K次的缓存
*/
for(auto it =cache_frame_.begin();it!=cache_frame_.end();it++)
{
auto frame = (*it).first;
if(evictable_[frame])
{
recorded_cnt_[frame] = 0;
cache_frame_.erase(it);
cache_locate_.erase(frame);
*frame_id = frame;
curr_size_--;
hist[frame].clear();
return true;
}
}
return false;
}
void LRUKReplacer::RecordAccess(frame_id_t frame_id)
{
std::lock_guard<std::mutex> lock(latch_);
if(frame_id>static_cast<frame_id_t>(replacer_size_))
{
throw std::exception();
}
current_timestamp_++;
recorded_cnt_[frame_id]++;
auto cnt = recorded_cnt_[frame_id];
hist[frame_id].push_back(current_timestamp_);
/**
* 如果是新加入的记录
*/
if(cnt==1)
{
if(curr_size_==max_size_)
{
frame_id_t frame;
Evict(&frame);
}
evictable_[frame_id] = true;
curr_size_++;
new_frame_.push_front(frame_id);
new_locate_[frame_id] = new_frame_.begin();
}
/**
* 如果记录达到k次,则需要从新队列中加入到老队列中
*/
if(cnt==k_)
{
new_frame_.erase(new_locate_[frame_id]);//从新队列中删除
new_locate_.erase(frame_id);
auto kth_time = hist[frame_id].front();//获取当前页面的倒数第k次出现的时间
k_time new_cache(frame_id,kth_time);
auto it = std::upper_bound(cache_frame_.begin(),cache_frame_.end(),new_cache,CmpTimestamp);//找到该插入的位置
it = cache_frame_.insert(it,new_cache);
cache_locate_[frame_id] = it;
return;
}
/**
* 如果记录在k次以上,需要将该frame放到指定的位置
*/
if(cnt>k_)
{
hist[frame_id].erase(hist[frame_id].begin());
cache_frame_.erase(cache_locate_[frame_id]);//去除原来的位置
auto kth_time = hist[frame_id].front();//获取当前页面的倒数第k次出现的时间
k_time new_cache(frame_id,kth_time);
auto it = std::upper_bound(cache_frame_.begin(),cache_frame_.end(),new_cache, CmpTimestamp);//找到该插入的位置
it = cache_frame_.insert(it,new_cache);
cache_locate_[frame_id] = it;
return;
}
/**
* 如果cnt
}
void LRUKReplacer::SetEvictable(frame_id_t frame_id, bool set_evictable)
{
std::lock_guard<std::mutex> lock(latch_);
if(recorded_cnt_[frame_id]==0)
{
return ;
}
auto status = evictable_[frame_id];
evictable_[frame_id] = set_evictable;
if(status&&!set_evictable)
{
--max_size_;
--curr_size_;
}
if(!status&&set_evictable)
{
++max_size_;
++curr_size_;
}
}
void LRUKReplacer::Remove(frame_id_t frame_id) {
std::lock_guard<std::mutex> lock(latch_);
if (frame_id > static_cast<frame_id_t>(replacer_size_)) {
throw std::exception();
}
auto cnt = recorded_cnt_[frame_id];
if (cnt == 0)
{
return ;
}
if(!evictable_[frame_id])
{
throw std::exception();
}
if(cnt<k_)
{
new_frame_.erase(new_locate_[frame_id]);
new_locate_.erase(frame_id);
recorded_cnt_[frame_id] = 0;
hist[frame_id].clear();
curr_size_--;
}
else
{
cache_frame_.erase(cache_locate_[frame_id]);
cache_locate_.erase(frame_id);
recorded_cnt_[frame_id] = 0;
hist[frame_id].clear();
curr_size_--;
}
}
auto LRUKReplacer::Size() -> size_t { return curr_size_; }
auto LRUKReplacer::CmpTimestamp(const LRUKReplacer:: k_time &f1,const LRUKReplacer:: k_time &f2) -> bool {
return f1.second<f2.second;
}
TEST(LRUKReplacerTest, AntiO2)
{
LRUKReplacer lru_replacer(3, 3);
frame_id_t frame;
ASSERT_EQ(lru_replacer.Size(),0);
lru_replacer.RecordAccess(1);
lru_replacer.RecordAccess(1);
lru_replacer.RecordAccess(1);
lru_replacer.RecordAccess(2);
lru_replacer.RecordAccess(2);
lru_replacer.RecordAccess(2);
lru_replacer.RecordAccess(1);
ASSERT_EQ(lru_replacer.Size(),2);
lru_replacer.RecordAccess(3);
lru_replacer.Evict(&frame);
ASSERT_EQ(frame,3);
lru_replacer.Evict(&frame);
EXPECT_EQ(frame,1);
lru_replacer.RecordAccess(1);
lru_replacer.RecordAccess(3);
lru_replacer.RecordAccess(1);
lru_replacer.Evict(&frame);
EXPECT_EQ(frame,1);
lru_replacer.RecordAccess(3);
lru_replacer.RecordAccess(3);
lru_replacer.Evict(&frame);
EXPECT_EQ(frame,2);
lru_replacer.Evict(&frame);
EXPECT_EQ(frame,3);
}