設計並實現一個LRU Cache

一、什麼是Cache

1 概念

Cache,即高速緩存,是介於CPU和內存之間的高速小容量存儲器。在金字塔式存儲體系中它位於自頂向下的第二層,僅次於CPU寄存器。其容量遠小於內存,但速度卻可以接近CPU的頻率。

當CPU發出內存訪問請求時,會先查看 Cache 內是否有請求數據。

  • 如果存在(命中),則直接返回該數據;
  • 如果不存在(失效),再去訪問內存 —— 先把內存中的相應數據載入緩存,再將其返回處理器。

提供“高速緩存”的目的是讓數據訪問的速度適應CPU的處理速度,通過減少訪問內存的次數來提高數據存取的速度。

2 原理

Cache 技術所依賴的原理是”程序執行與數據訪問的局部性原理“,這種局部性表現在兩個方面:

  1. 時間局部性:如果程序中的某條指令一旦執行,不久以後該指令可能再次執行,如果某數據被訪問過,不久以後該數據可能再次被訪問。
  2. 空間局部性:一旦程序訪問了某個存儲單元,在不久之後,其附近的存儲單元也將被訪問,即程序在一段時間內所訪問的地址,可能集中在一定的範圍之內,這是因爲指令或數據通常是順序存放的。

時間局部性是通過將近來使用的指令和數據保存到Cache中實現。空間局部性通常是使用較大的高速緩存,並將 預取機制 集成到高速緩存控制邏輯中來實現。

3 替換策略

Cache的容量是有限的,當Cache的空間都被佔滿後,如果再次發生緩存失效,就必須選擇一個緩存塊來替換掉。常用的替換策略有以下幾種:

  1. 隨機算法(Rand):隨機法是隨機地確定替換的存儲塊。設置一個隨機數產生器,依據所產生的隨機數,確定替換塊。這種方法簡單、易於實現,但命中率比較低。

  2. 先進先出算法(FIFO, First In First Out):先進先出法是選擇那個最先調入的那個塊進行替換。當最先調入並被多次命中的塊,很可能被優先替換,因而不符合局部性規律。這種方法的命中率比隨機法好些,但還不滿足要求。

  3. 最久未使用算法(LRU, Least Recently Used):LRU法是依據各塊使用的情況, 總是選擇那個最長時間未被使用的塊替換。這種方法比較好地反映了程序局部性規律。

  4. 最不經常使用算法(LFU, Least Frequently Used):將最近一段時期內,訪問次數最少的塊替換出Cache。

4 概念的擴充

如今高速緩存的概念已被擴充,不僅在CPU和主內存之間有Cache,而且在內存和硬盤之間也有Cache(磁盤緩存),乃至在硬盤與網絡之間也有某種意義上的Cache──稱爲Internet臨時文件夾或網絡內容緩存等。凡是位於速度相差較大的兩種硬件之間,用於協調兩者數據傳輸速度差異的結構,均可稱之爲Cache。


二、LRU Cache的實現

Google的一道面試題:

Design an LRU cache with all the operations to be done in O(1) .

1 思路分析

對一個Cache的操作無非三種:插入(insert)、替換(replace)、查找(lookup)。

爲了能夠快速刪除最久沒有訪問的數據項和插入最新的數據項,我們使用 雙向鏈表 連接Cache中的數據項,並且保證鏈表維持數據項從最近訪問到最舊訪問的順序。

  • 插入:當Cache未滿時,新的數據項只需插到雙鏈表頭部即可。時間複雜度爲O(1) .

  • 替換:當Cache已滿時,將新的數據項插到雙鏈表頭部,並刪除雙鏈表的尾結點即可。時間複雜度爲O(1) .

  • 查找:每次數據項被查詢到時,都將此數據項移動到鏈表頭部。

經過分析,我們知道使用雙向鏈表可以保證插入和替換的時間複雜度是O(1) ,但查詢的時間複雜度是O(n) ,因爲需要對雙鏈表進行遍歷。爲了讓查找效率也達到O(1) ,很自然的會想到使用 hash table

2 代碼實現

從上述分析可知,我們需要使用兩種數據結構:

  1. 雙向鏈表(Doubly Linked List)
  2. 哈希表(Hash Table)

下面是LRU Cache的 C++ 實現:

#include <iostream>
#include <unordered_map>
using namespace std;

// 雙向鏈表的節點結構
struct LRUCacheNode {
    int key;
    int value;
    LRUCacheNode* prev;
    LRUCacheNode* next;
    LRUCacheNode():key(0),value(0),prev(NULL),next(NULL){}
};


class LRUCache
{
private:
    unordered_map<int, LRUCacheNode*> m;  // 代替hash_map
    LRUCacheNode* head;     // 指向雙鏈表的頭結點
    LRUCacheNode* tail;     // 指向雙鏈表的尾結點
    int capacity;           // Cache的容量
    int count;              // 計數
public:
    LRUCache(int capacity);       // 構造函數
    ~LRUCache();                  // 析構函數
    int get(int key);             // 查詢數據項
    void set(int key, int value); // 未滿時插入,已滿時替換
private:
    void removeLRUNode();                 // 刪除尾結點(最久未使用)
    void detachNode(LRUCacheNode* node);    // 分離當前結點
    void insertToFront(LRUCacheNode* node); // 節點插入到頭部
};


LRUCache::LRUCache(int capacity)
{
    this->capacity = capacity;
    this->count = 0;
    head = new LRUCacheNode;
    tail = new LRUCacheNode;
    head->prev = NULL;
    head->next = tail;
    tail->prev = head;
    tail->next = NULL;
}

LRUCache::~LRUCache()
{
    delete head;
    delete tail;
}

int LRUCache::get(int key)
{
    if(m.find(key) == m.end())  // 沒找到
        return -1;
    else
    {
        LRUCacheNode* node = m[key];
        detachNode(node);      // 命中,移至頭部 
        insertToFront(node);
        return node->value;
    }
}

void LRUCache::set(int key, int value)
{
    if(m.find(key) == m.end())  // 沒找到
    {
        LRUCacheNode* node = new LRUCacheNode;
        if(count == capacity)   // Cache已滿
            removeLRUNode();

        node->key = key;
        node->value = value;
        m[key] = node;          // 插入哈希表
        insertToFront(node);    // 插入鏈表頭部
        ++count;
    }
    else
    {
        LRUCacheNode* node = m[key];
        detachNode(node);
        node->value = value;
        insertToFront(node);
    }
}

void LRUCache::removeLRUNode()
{
    LRUCacheNode* node = tail->prev;
    detachNode(node);
    m.erase(node->key);
    --count;
}

void LRUCache::detachNode(LRUCacheNode* node)
{
    node->prev->next = node->next;
    node->next->prev = node->prev;
}


void LRUCache::insertToFront(LRUCacheNode* node)
{
    node->next = head->next;
    node->prev = head;
    head->next = node;
    node->next->prev = node;
}







個人站點:http://songlee24.github.com

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章