• 缓存算法(页面置换算法)总结


    首先解释一下,缓存算法和内存页面置换算法(Page Replacement Algorithm)的核心思想是一样的,都是给定一个有限的空间,设计一个算法来更新和访问里面的数据,所以把它们放在一起讨论总结。下面提到缓存算法的同时,也指代页面置换算法。

    常见的缓存算法有 FIFO、Least Recently Used (LRU)、Least Frequently Used (LFU)。

    FIFO

    FIFO 算法是一种比较容易实现的算法。它的思想是先进先出(FIFO,队列),这是最简单、最公平的一种思想,即如果一个数据是最先进入的,那么可以认为在将来它被访问的可能性很小。空间满的时候,最先进入的数据会被最早置换(淘汰)掉

    FIFO 算法的描述:设计一种缓存结构,该结构在构造时确定大小,假设大小为 K,并有两个功能:

    1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将最先进入缓存的数据置换掉。
    2. get(key):返回key对应的value值。

    实现:维护一个FIFO队列,按照时间顺序将各数据(已分配页面)链接起来组成队列,并将置换指针指向队列的队首。再进行置换时,只需把置换指针所指的数据(页面)顺次换出,并把新加入的数据插到队尾即可。

    缺点:判断一个页面置换算法优劣的指标就是缺页率,而FIFO算法的一个显著的缺点是,在某些特定的时刻,缺页率反而会随着分配页面的增加而增加,这称为Belady现象。产生Belady现象现象的原因是,FIFO置换算法与进程访问内存的动态特征是不相容的,被置换的内存页面往往是被频繁访问的,或者没有给进程分配足够的页面,因此FIFO算法会使一些页面频繁地被替换和重新申请内存,从而导致缺页率增加。因此,现在不再使用FIFO算法。

    LRU

    LRU(The Least Recently Used,最近最久未使用算法)是一种常见的缓存算法,在很多分布式缓存系统(如Redis, Memcached)中都有广泛使用。

    LRU算法的思想是:如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最久没有访问的数据最先被置换(淘汰)

    LRU算法的描述: 设计一种缓存结构,该结构在构造时确定大小,假设大小为 K,并有两个功能:

    1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将最久未使用的数据置换掉。
    2. get(key):返回key对应的value值。

    实现:最朴素的思想就是用数组+时间戳的方式,不过这样做效率较低。因此,我们可以用双向链表(LinkedList)+哈希表(HashMap)实现(链表用来表示位置,哈希表用来存储和查找),在Java里有对应的数据结构LinkedHashMap

    LeetCode上有关于LRU的一道题(LeetCode #146):

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.

    • get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
    • set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

    这里我用了双向链表+哈希表实现的。

    C++ code(72 ms):

    #define DEFAULT_LIST_SIZE 10
    class LRUCache {
        private:
            class Node {
                public:
                    int key;
                    int value;
                    Node* pre;
                    Node* next;
                    Node(int key,int value,Node* pre,Node* next) {
                        this->key = key;
                        this->value = value;
                        this->pre = pre;
                        this->next = next;
                    }
            };
            int count;
            int size;
            unordered_map<int,Node*> mp;
            Node* cacheHead;
            Node* cacheTail;
            void push_front(Node* cur) {
                if(count == 1 || cur == cacheHead) {
                    return;
                }
                if(cur == cacheTail) {
                    cacheTail = cur->pre;
                }
                cur->pre->next = cur->next;
                if(cur->next != nullptr) {
                    cur->next->pre = cur->pre;
                }
                cur->next = cacheHead;
                cur->pre = nullptr;
                cacheHead->pre = cur;
                cacheHead = cur;
            }
        public:
            LRUCache() {
                this->size = DEFAULT_LIST_SIZE;
                this->count = 0;
                this->cacheHead = nullptr;
                this->cacheTail = nullptr;
            }
            LRUCache(int capacity):size(capacity) {
                this->count = 0;
                this->cacheHead = nullptr;
                this->cacheTail = nullptr;
            }
            void set(int key, int value) {
                if(cacheHead == nullptr) {
                    cacheHead = new Node(key,value,nullptr,nullptr);
                    mp[key] = cacheHead;
                    cacheTail = cacheHead;
                    count++;
                }
                else {
                    unordered_map<int,Node*>::iterator it = mp.find(key);
                    if(it == mp.end()) {
                        if(count == size) {
                            if(cacheHead == cacheTail && cacheHead != nullptr) {
                                mp.erase(cacheHead->key);
                                cacheHead->key = key;
                                cacheHead->value = value;
                                mp[key] = cacheHead;
                            }
                            else {
                                Node *p = cacheTail;
                                cacheTail->pre->next = cacheTail->next;
                                cacheTail = cacheTail->pre;
                                mp.erase(p->key);
                                p->key = key;
                                p->value = value;
                                p->next = cacheHead;
                                p->pre = cacheHead->pre;
                                cacheHead->pre = p;
                                cacheHead = p;
                                mp[cacheHead->key] = cacheHead;
                            }
                        }
                        else {
                            Node* p = new Node(key,value,nullptr,cacheHead);
                            cacheHead->pre = p;
                            cacheHead = p;
                            mp[cacheHead->key] = cacheHead;
                            count++;    
                        }
                    }
                    else {
                        Node *p = it->second;
                        p->value = value;
                        pushFront(p);
                    }
                }
            }
            int get(int key) {
                if(cacheHead == nullptr)
                    return -1;
                unordered_map<int,Node*>::iterator it = mp.find(key);
                if(it == mp.end()) {
                    return -1;
                }
                else {
                    Node* p = it->second;
                    pushFront(p);
                }
                return cacheHead->value;
            }
    };

    LFU 算法

    LFU(Least Frequently Used ,最近最少使用算法)也是一种常见的缓存算法。

    顾名思义,LFU算法的思想是:如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰

    LFU 算法的描述:

    设计一种缓存结构,该结构在构造时确定大小,假设大小为 K,并有两个功能:

    1. set(key,value):将记录(key,value)插入该结构。当缓存满时,将访问频率最低的数据置换掉。
    2. get(key):返回key对应的value值。

    算法实现策略:考虑到 LFU 会淘汰访问频率最小的数据,我们需要一种合适的方法按大小顺序维护数据访问的频率。LFU 算法本质上可以看做是一个 top K 问题(K = 1),即选出频率最小的元素,因此我们很容易想到可以用二项堆来选择频率最小的元素,这样的实现比较高效。最终实现策略为小顶堆+哈希表。

    OPT算法

    最佳页面置换算法(OPT,Bélády’s Algorithm)是一种理论上最佳的页面置换算法。它的思想是,试图淘汰掉以后永远也用不到的页面,如果没有则淘汰最久以后再用到的页面。因为这种算法必须知道进程访问页面的序列,而这是无法实现的,因此仅有理论意义。

  • 相关阅读:
    Graph neural networks: A review of methods and applications文献阅读
    IMBD数据集处理
    GNN知识整理(二)
    GNN认识整理(一)
    Linux中python中的#!/usr/bin/python
    Linux下运行g++
    itextpdf7自写算法的表格展示 制表符
    itext7 List序号 有序列表 解决中文不显示
    java使用itextpdf7实现导出pdf表格;java使用itextpdf7实现pdf加水印
    csv导出导入工具类 commons-csv导出
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13312571.html
Copyright © 2020-2023  润新知