LRU算法–緩存淘汰算法

目錄

1. 什麼是 LRU

LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那麼將來被訪問的機率也很高”,反過來說“如果數據最近這段時間一直都沒有訪問,那麼將來被訪問的概率也會很低”,兩種理解是一樣的;常用於頁面置換算法,是爲虛擬頁式存儲管理服務的。

2. LRU 算法思想

達到這樣一種情形的算法是最理想的:每次調換出的頁面是所有內存頁面中最遲將被使用的;這可以最大限度的推遲頁面調換,這種算法,被稱爲理想頁面置換算法。可惜的是,這種算法是無法實現的。
爲了儘量減少與理想算法的差距,產生了各種精妙的算法,最近最少使用頁面置換算法便是其中一個。LRU 算法的提出,是基於這樣一個事實:在前面幾條指令中使用頻繁的頁面很可能在後面的幾條指令中頻繁使用。反過來說,已經很久沒有使用的頁面很可能在未來較長的一段時間內不會被用到 。這個,就是著名的局部性原理——比內存速度還要快的cache,也是基於同樣的原理運行的。因此,我們只需要在每次調換時,找到最近最少使用的那個頁面調出內存。

注意:操作系統分頁處理使用的分頁調度算法是LRU算法的近似實現:Clock算法

3.爲什麼需要頁面置換算法?

  • 內存是有限的,不可能把所有的頁面都裝進來
    – 缺頁時需要進行頁面置換

  • 頁面置換的背後是個通用的問題
    –Web服務器的緩存
    –Redis ,memcached 的緩存
    –……

4.分頁原理圖

這裏寫圖片描述

5. 假設使用 FIFO (先進先出) 實現頁面置換算法

這裏寫圖片描述

很明顯, 按照FIFO算法, 雖然一個頁面被頻繁訪問, 它還是很有可能被置換出去。

6. LRU 算法原理圖

這裏寫圖片描述

7. 使用雙向鏈表實現LRU算法

算法實現:

public class LRUPageFrame {
    private static class Node{
        Node prev;
        Node next;
        int pageNum;
        Node(){

        }
    }

    private int capacity;//容量

    private int currentSize;
    private Node first;
    private Node last;

    public LRUPageFrame( int capacity ) {
        this.currentSize = 0;
        this.capacity  = capacity;
    }

    /**
     * 獲得緩存中的對象
     * @param pageNum
     */
    public void access( int pageNum ){
        Node node = find( pageNum );
        if( node != null ){
            moveExistingNodeToHead(node);
        } else {

            node = new Node();
            node.pageNum = pageNum;

            // 緩存容器是否已經超過大小.
            if( currentSize >= capacity ){
                removeLast(); 
            }

            addNewNodetoHead(node);
        } 
    }
    private Node find( int data ){
        Node node = first;
        while( node != null ){
            if( node.pageNum == data ){
                return node;
            }
            node = node.next;
        }
        return null;
    }


    /**
     * 移動到鏈表頭,表示這個節點是最新使用過的
     * @param node
     */
    private void moveExistingNodeToHead( Node node ){
        if( node == first ){
            return;
        } else if( node == last ){
            //當前節點是鏈表尾, 需要放到鏈表頭
            Node prevNode = node.prev;
            prevNode.next = null;
            last.prev = null;
            last = prevNode;
        } else {
            //node 在鏈表的中間, 把node 的前後節點連接起來
            Node prevNode = node.prev;
            prevNode.next = node.next;

            Node nextNode = node.next;
            nextNode.prev = prevNode;
        }
        node.prev = null;
        node.next = first;
        first.prev = node;
        first = node;
    }

    /**
     * 刪除鏈表尾部節點 表示 刪除最少使用的緩存對象
     */
    private void removeLast(){
        Node prevNode = last.prev;
        prevNode.next = null;
        last.prev = null;
        last = prevNode;
        this.currentSize --;
    }

    private void addNewNodetoHead( Node node ){
        if( isEmpty() ){
            node.prev = null;
            node.next = null;
            first = node;
            last = node;
        } else {
            node.prev = null;
            node.next = first;
            first.prev = node;
            first = node;
        }
        this.currentSize ++;
    }

    private boolean isEmpty(){
        return (first == null) && (last == null);
    }

    public String toString(){
        StringBuilder buffer = new StringBuilder();
        Node node = first;
        while( node != null ){
            buffer.append( node.pageNum );
            node = node.next;
            if( node != null ){
                buffer.append(",");
            }
        }
        return buffer.toString();
    }
}

測試用例:

public class LRUPageFrameTest {
    @Test
    public void testAccess() {
        LRUPageFrame frame = new LRUPageFrame(3);
        frame.access(7);
        frame.access(0);
        frame.access(1);
        Assert.assertEquals("1,0,7", frame.toString());
        frame.access(2);
        Assert.assertEquals("2,1,0", frame.toString());
        frame.access(0);
        Assert.assertEquals("0,2,1", frame.toString());
        frame.access(0);
        Assert.assertEquals("0,2,1", frame.toString());
        frame.access(3);
        Assert.assertEquals("3,0,2", frame.toString());
        frame.access(0);
        Assert.assertEquals("0,3,2", frame.toString());
        frame.access(4);
        Assert.assertEquals("4,0,3", frame.toString());
        frame.access(5);
        Assert.assertEquals("5,4,0", frame.toString());
    }   
}

8.Clock算法是公認的很好的近似LRU的算法

只有三個物理頁面 邏輯頁面的訪問次序是:
3、4、2、6、4、3

這裏寫圖片描述

  • 每個頁加一個引用位, 默認值爲0,無論讀還是寫,都置爲1

  • 把所有的頁組成一個循環隊列

  • 選擇淘汰頁的時候,掃描引用位, 如果是1則改爲0(相當於再給該頁面一次存活的機會), 並掃描下一個;如果該引用位是0, 則淘汰該頁, 換入新的頁面

    腳註
    前兩節內容節選自—— [ MinHow-個人博客 ]

發佈了29 篇原創文章 · 獲贊 46 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章