CMU 15-445 Project #1 - Buffer Pool(Task #2 - LRU-K Replacement Policy)

Task #2 - LRU-K Replacement Policy

  • 一、题目链接
  • 二、准备工作
  • 三、部分实现
  • 四、自定义测试用例

在这里插入图片描述

一、题目链接


二、准备工作

见 CMU 15-445 Project #0 - C++ Primer 中的准备工作。


三、部分实现

LRU-K的提出是为了更好地解决传统LRU中的缓存污染问题。所谓缓存污染,是指突发的不常用数据的访问可能会挤出缓存中的常用数据,从而导致缓存的效果下降。而LRU-K通过增加了一个历史队列,保证了不常用数据的突发访问不会干扰缓存队列中的常用数据缓存。

根据课程文档的描述,历史队列需要通过FIFO来进行缓存淘汰,而缓存队列则需要根据 k-distance(可是雪啊,飘进双眼~)来进行缓存淘汰,也即依据倒数第k次访问的时间戳来进行淘汰。因此缓存队列使用的并非传统的LRU,一个缓存队列中的页框尽管刚刚被访问,但它仍有可能被立即淘汰。

比如当k值为 3 时有访问序列 1->1->1->2->2->2->1,此时 页框1页框2 均被放入缓存队列,如果按照传统LRU,页框1 刚刚被访问,因此会淘汰 页框2,但如果按照LRU-K,页框1 的倒数第三次访问更早,因此正确的淘汰页框应该是 页框1

LRUKReplacer部分成员变量及页框实体Frame的定义

class LRUKReplacer {
public:
    class Frame {
    public:
        Frame(std::list<frame_id_t>::iterator iter, size_t timestamp) : iter_(iter) {
            timestamp_list_.emplace_back(timestamp);
        }

    public:
        size_t access_count_ = 1;               // 该页框被访问的次数,与timestamp_list_的长度一致
        bool evictable_ = true;                 // 该页框是否可以被驱逐
        std::list<size_t> timestamp_list_;      // 该页框的历史访问时间戳序列,长度最大值为k_
        std::list<frame_id_t>::iterator iter_;  // 该页框对应的frame_id在历史队列或缓存队列中的迭代器,用于提高查找效率
    };

	...

private:
    size_t current_timestamp_ = 0;  // 当前时间戳
    size_t curr_size_ = 0;          // 当前可驱逐的页框总数
    size_t replacer_size_;          // 内存所能容纳的最大页框数
    size_t k_;                      // LRU-K中的K值
    std::mutex latch_;              // 互斥锁

    std::list<frame_id_t> history_list_;                  // 历史队列,使用FIFO进行淘汰处理
    std::list<frame_id_t> cache_list_;                    // 缓存队列,根据k-distance进行淘汰处理
    std::unordered_map<frame_id_t, Frame> id_frame_map_;  // 映射页框号和其对应的页框实体
};

RecordAccess

void RecordAccess(frame_id_t frame_id) {
    std::scoped_lock<std::mutex> locker(latch_);

    /* frame_id不能超过replacer_size_,由此来限制页框总数。 */
    if (frame_id > static_cast<int>(replacer_size_)) {
        return;
    }

    /* 如果frame_id不在id_frame_map_中,表明是首次访问,需要将frame_id插入到历史队列队尾,同时新建页框实体并建立与frame_id的映射;
     * 否则需要根据+1后的访问次数与k_的关系进行相应处理。 */
    auto cur_pair = id_frame_map_.find(frame_id);
    if (cur_pair == id_frame_map_.end()) {
        history_list_.emplace_back(frame_id);
        Frame new_frame(std::prev(history_list_.end()), current_timestamp_);
        id_frame_map_.emplace(frame_id, new_frame);
        curr_size_++;
    } else {
        /* 增加访问次数 */
        cur_pair->second.access_count_++;

        /* 如果访问次数小于k次,只需要扩充访问时间序列;
         * 如果访问次数等于k次,需要扩充访问时间序列,然后将frame_id从历史队列删除,并根据倒数第k次的访问时间插入到缓存队列相应位置;
         * 如果访问次数大于k次,需要更新访问时间序列,并根据倒数第k次的访问时间将frame_id移动到缓存队列相应位置。 */
        if (cur_pair->second.access_count_ < k_) {
            /* 扩充访问时间序列 */
            cur_pair->second.timestamp_list_.emplace_back(current_timestamp_);
        } else if (cur_pair->second.access_count_ == k_) {
            /* 扩充访问时间序列 */
            cur_pair->second.timestamp_list_.emplace_back(current_timestamp_);

            /* 将frame_id从历史队列删除 */
            history_list_.erase(cur_pair->second.iter_);

            /* 根据倒数第k次的访问时间将frame_id插入到缓存队列相应位置 */
            size_t k_timestamp = cur_pair->second.timestamp_list_.front();
            auto iter = cache_list_.begin();
            while (iter != cache_list_.end() && k_timestamp > id_frame_map_.at(*iter).timestamp_list_.front()) {
                iter++;
            }
            cur_pair->second.iter_ = cache_list_.insert(iter, frame_id);  // 插入后要修改对应的迭代器
        } else {
            /* 更新访问时间序列 */
            cur_pair->second.timestamp_list_.pop_front();
            cur_pair->second.timestamp_list_.emplace_back(current_timestamp_);

            /* 根据倒数第k次的访问时间将frame_id移动到缓存队列相应位置 */
            cache_list_.erase(cur_pair->second.iter_);
            size_t k_timestamp = cur_pair->second.timestamp_list_.front();
            auto iter = cache_list_.begin();
            while (iter != cache_list_.end() && k_timestamp > id_frame_map_.at(*iter).timestamp_list_.front()) {
                iter++;
            }
            cur_pair->second.iter_ = cache_list_.insert(iter, frame_id);  // 插入后要修改对应的迭代器
        }
    }

    /* 时间增加 */
    current_timestamp_++;
}

Evict

auto Evict(frame_id_t *frame_id) -> bool {
    std::scoped_lock<std::mutex> locker(latch_);

    /* 没有允许驱逐的页框 */
    if (curr_size_ == 0) {
        return false;
    }

    /* 如果历史队列不为空,优先按FIFO从历史队列中驱逐 */
    if (!history_list_.empty()) {
        for (auto iter = history_list_.begin(); iter != history_list_.end(); iter++) {
            if (id_frame_map_.at(*iter).evictable_) {
                *frame_id = *iter;
                id_frame_map_.erase(*iter);
                history_list_.erase(iter);
                curr_size_--;
                return true;
            }
        }
    }

    /* 如果没能从历史队列中驱逐,则从缓存队列中按照依据k-distance驱逐 */
    if (!cache_list_.empty()) {
        for (auto iter = cache_list_.begin(); iter != cache_list_.end(); iter++) {
            if (id_frame_map_.at(*iter).evictable_) {
                *frame_id = *iter;
                id_frame_map_.erase(*iter);
                cache_list_.erase(iter);
                curr_size_--;
                return true;
            }
        }
    }

    return false;
}

SetEvictable

void SetEvictable(frame_id_t frame_id, bool set_evictable) {
    std::scoped_lock<std::mutex> locker(latch_);

    auto cur_pair = id_frame_map_.find(frame_id);

    /* frame_id不存在 */
    if (cur_pair == id_frame_map_.end()) {
        return;
    }

    /* 根据evictable_的初始值和目标值修改curr_size_ */
    if (cur_pair->second.evictable_ && !set_evictable) {
        curr_size_--;
    } else if (!cur_pair->second.evictable_ && set_evictable) {
        curr_size_++;
    }

    cur_pair->second.evictable_ = set_evictable;
}

Remove

void Remove(frame_id_t frame_id) {
    std::scoped_lock<std::mutex> locker(latch_);

    auto cur_pair = id_frame_map_.find(frame_id);

    /* 对应页框不存在 */
    if (cur_pair == id_frame_map_.end()) {
        return;
    }

    /* 对应页框不可驱逐 */
    if (!cur_pair->second.evictable_) {
        return;
    }

    /* 根据对应页框的引用次数从历史队列或缓存队列中将其删除,同时也要删除id_frame_map_中的映射 */
    if (cur_pair->second.access_count_ >= k_) {
        cache_list_.erase(cur_pair->second.iter_);
    } else {
        history_list_.erase(cur_pair->second.iter_);
    }
    id_frame_map_.erase(cur_pair);

    curr_size_--;
}

Size

auto Size() -> size_t {
    std::scoped_lock<std::mutex> locker(latch_);

    return curr_size_;
}

提交后的结果如下,LRU-K的所有测试用例全部通过:

CMU 15-445 Project #1 - Buffer Pool(Task #2 - LRU-K Replacement Policy)_第1张图片


四、自定义测试用例

由于默认提供的本地测试用例过于简单,很多问题都无法排查,因此这里分享两个个稍微复杂一点的测试用例:

TEST(LRUKReplacerTest, EvictTest1) {
    LRUKReplacer lru_replacer(3, 3);
    frame_id_t frame_id;

    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_id);
    ASSERT_EQ(frame_id, 3);

    lru_replacer.Evict(&frame_id);
    EXPECT_EQ(frame_id, 1);

    lru_replacer.RecordAccess(1);
    lru_replacer.RecordAccess(3);
    lru_replacer.RecordAccess(1);

    lru_replacer.Evict(&frame_id);
    EXPECT_EQ(frame_id, 1);

    lru_replacer.RecordAccess(3);
    lru_replacer.RecordAccess(3);

    lru_replacer.Evict(&frame_id);
    EXPECT_EQ(frame_id, 2);

    lru_replacer.Evict(&frame_id);
    EXPECT_EQ(frame_id, 3);
}

TEST(LRUKReplacerTest, EvictTest2) {
    LRUKReplacer lru_replacer(3, 3);
    frame_id_t frame_id;

    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(3);
    lru_replacer.RecordAccess(3);
    lru_replacer.RecordAccess(3);
    lru_replacer.RecordAccess(2);

    ASSERT_EQ(lru_replacer.Size(), 3);

    lru_replacer.Evict(&frame_id);
    EXPECT_EQ(frame_id, 1);

    lru_replacer.Evict(&frame_id);
    EXPECT_EQ(frame_id, 2);

    lru_replacer.Evict(&frame_id);
    EXPECT_EQ(frame_id, 3);
}

参考 :

https://blog.csdn.net/Altair_alpha/article/details/127745308
https://blog.csdn.net/AntiO2/article/details/128439155

在这里插入图片描述

你可能感兴趣的:(CMU,15-445(FALL,2022),c++,算法,数据库内核)