2010-03-23 124 views
59

最近最少使用(LRU)緩存將丟棄最近最少使用的項目 您如何設計和實現這樣的緩存類?設計要求如下:LRU緩存設計

1)以最快的速度找到該項目,我們可以

2)一旦高速緩存未命中和緩存已滿,我們需要快速更換最近最少使用的物品儘可能。

如何根據設計模式和算法設計來分析和實現這個問題?

+1

參見http://stackoverflow.com/questions/3639744/least-recently-used-cache-using-c和http://計算器。 com/questions/2057424/lru-implementation-in-production-code – timday 2010-12-15 13:26:55

回答

88

指向鏈表節點的鏈表+哈希表是實現LRU緩存的常用方式。這給了O(1)操作(假設一個體面的散列)。這個優點(作爲O(1)):你可以通過鎖定整個結構來執行多線程版本。你不必擔心粒狀鎖定等

簡單地說,它的工作方式:

在一個值的訪問,您將相應的節點鏈表頭部。

當您需要從緩存中刪除值時,您將從尾部刪除。

將值添加到緩存時,只需將其放置在鏈接列表的頭部即可。

感謝doublep,這裏是C++實現的網站:Miscellaneous Container Templates

+4

@Moron:我會使用一個雙向鏈表。 – 2010-03-24 03:03:15

+2

@darid:我認爲你也可以使用一個單鏈表,並使散列表指向* previous *節點到包含散列值的節點。 – 2010-03-24 09:38:07

+0

@darid:只是'鏈表'並不意味着單鏈表。另外,對於單鏈表,製作一個隨機節點的頭不是O(1)。 – 2010-03-24 18:07:30

0

緩存一個數據結構,它支持像哈希表一樣的檢索值? LRU意味着緩存具有一定的大小限制,我們需要定期丟棄最少使用的條目。

如果你使用指針的鏈表+哈希表實現你怎麼做O(1)通過鍵檢索值?

我會用散列表實現LRU緩存,每個條目的值都是value +指向prev/next條目的指針。

關於多線程訪問,我寧願讀者鎖定器(理想情況下通過自旋鎖實現,因爲爭用通常很快)來監視。

+2

你所說的幾乎是鏈接列表+散列表方法。 – sprite 2011-03-30 16:09:54

18

這是我對LRU緩存的簡單示例C++實現,結合了hash(unordered_map)和list。列表中的項目具有訪問地圖的關鍵,並且地圖上的項目具有訪問列表的列表的迭代器。

#include <list> 
#include <unordered_map> 
#include <assert.h> 

using namespace std; 

template <class KEY_T, class VAL_T> class LRUCache{ 
private: 
     list< pair<KEY_T,VAL_T> > item_list; 
     unordered_map<KEY_T, decltype(item_list.begin()) > item_map; 
     size_t cache_size; 
private: 
     void clean(void){ 
       while(item_map.size()>cache_size){ 
         auto last_it = item_list.end(); last_it --; 
         item_map.erase(last_it->first); 
         item_list.pop_back(); 
       } 
     }; 
public: 
     LRUCache(int cache_size_):cache_size(cache_size_){ 
       ; 
     }; 

     void put(const KEY_T &key, const VAL_T &val){ 
       auto it = item_map.find(key); 
       if(it != item_map.end()){ 
         item_list.erase(it->second); 
         item_map.erase(it); 
       } 
       item_list.push_front(make_pair(key,val)); 
       item_map.insert(make_pair(key, item_list.begin())); 
       clean(); 
     }; 
     bool exist(const KEY_T &key){ 
       return (item_map.count(key)>0); 
     }; 
     VAL_T get(const KEY_T &key){ 
       assert(exist(key)); 
       auto it = item_map.find(key); 
       item_list.splice(item_list.begin(), item_list, it->second); 
       return it->second->second; 
     }; 

}; 
+1

爲什麼你使用列表和未定義的地圖? – jax 2016-04-18 18:14:18

+4

列表在內部實現爲雙向鏈表,而unordered_map基本上是一個哈希表。所以這是在時間和空間複雜度方面的有效解決方案。 – 2016-11-03 05:41:48

1

我有一個LRU實現here。接口遵循std :: map,所以它不應該很難使用。此外,您可以提供自定義備份處理程序,用於數據在緩存中失效時使用。

sweet::Cache<std::string,std::vector<int>, 48> c1; 
c1.insert("key1", std::vector<int>()); 
c1.insert("key2", std::vector<int>()); 
assert(c1.contains("key1")); 
1

這是我對基本的簡單LRU緩存的實現。

//LRU Cache 
#include <cassert> 
#include <list> 

template <typename K, 
      typename V 
      > 
class LRUCache 
    { 
    // Key access history, most recent at back 
    typedef std::list<K> List; 

    // Key to value and key history iterator 
    typedef unordered_map< K, 
          std::pair< 
            V, 
            typename std::list<K>::iterator 
            > 
         > Cache; 

    typedef V (*Fn)(const K&); 

public: 
    LRUCache(size_t aCapacity, Fn aFn) 
     : mFn(aFn) 
     , mCapacity(aCapacity) 
     {} 

    //get value for key aKey 
    V operator()(const K& aKey) 
     { 
     typename Cache::iterator it = mCache.find(aKey); 
     if(it == mCache.end()) //cache-miss: did not find the key 
      { 
      V v = mFn(aKey); 
      insert(aKey, v); 
      return v; 
      } 

     // cache-hit 
     // Update access record by moving accessed key to back of the list 
     mList.splice(mList.end(), mList, (it)->second.second); 

     // return the retrieved value 
     return (it)->second.first; 
     } 

private: 
     // insert a new key-value pair in the cache 
    void insert(const K& aKey, V aValue) 
     { 
     //method should be called only when cache-miss happens 
     assert(mCache.find(aKey) == mCache.end()); 

     // make space if necessary 
     if(mList.size() == mCapacity) 
      { 
      evict(); 
      } 

     // record k as most-recently-used key 
     typename std::list<K>::iterator it = mList.insert(mList.end(), aKey); 

     // create key-value entry, linked to the usage record 
     mCache.insert(std::make_pair(aKey, std::make_pair(aValue, it))); 
     } 

     //Purge the least-recently used element in the cache 
    void evict() 
     { 
     assert(!mList.empty()); 

     // identify least-recently-used key 
     const typename Cache::iterator it = mCache.find(mList.front()); 

     //erase both elements to completely purge record 
     mCache.erase(it); 
     mList.pop_front(); 
     } 

private: 
    List mList; 
    Cache mCache; 
    Fn mFn; 
    size_t mCapacity; 
    }; 
4

using hash table and doubly linked list to design LRU Cache

雙鏈表可以處理這個問題。根據關鍵字,地圖是跟蹤節點位置的好方法。

class LRUCache{ 

//define the double linked list, each node stores both the key and value. 
struct Node{ 
    Node* next; 
    Node* prev; 
    int value; 
    int key; 
    Node(Node* p, Node* n, int k, int val):prev(p),next(n),key(k),value(val){}; 
    Node(int k, int val):prev(NULL),next(NULL),key(k),value(val){}; 
}; 


map<int,Node*>mp; //map the key to the node in the linked list 
int cp; //capacity 
Node* tail; // double linked list tail pointer 
Node* head; // double linked list head pointer 



public: 
    //constructor 
    LRUCache(int capacity) { 
     cp = capacity; 
     mp.clear(); 
     head=NULL; 
     tail=NULL; 
    } 

    //insert node to the tail of the linked list 
    void insertNode(Node* node){ 
     if (!head){ 
      head = node; 
      tail = node; 
     }else{ 
      tail->next = node; 
      node->prev = tail; 
      tail = tail->next; 
     } 
    } 

    //remove current node 
    void rmNode(Node* node){ 
     if (node==head){ 
      head = head->next; 
      if (head){head->prev = NULL;} 
     }else{ 
      if (node==tail){ 
       tail =tail->prev; 
       tail->next = NULL; 
      }else{ 
       node->next->prev = node->prev; 
       node->prev->next = node->next; 
      } 
     } 
    } 

    // move current node to the tail of the linked list 
    void moveNode(Node* node){ 
     if (tail==node){ 
      return; 
     }else{ 
      if (node==head){ 
       node->next->prev = NULL; 
       head = node->next; 
       tail->next = node; 
       node->prev = tail; 
       tail=tail->next; 
      }else{ 
       node->prev->next = node->next; 
       node->next->prev = node->prev; 
       tail->next = node; 
       node->prev = tail; 
       tail=tail->next; 
      } 
     } 
    } 


    /////////////////////////////////////////////////////////////////////// 
    // get method 
    /////////////////////////////////////////////////////////////////////// 
    int get(int key) { 
     if (mp.find(key)==mp.end()){ 
      return -1; 
     }else{ 
      Node *tmp = mp[key]; 
      moveNode(tmp); 
      return mp[key]->value; 
     } 
    } 

    /////////////////////////////////////////////////////////////////////// 
    // set method 
    /////////////////////////////////////////////////////////////////////// 
    void set(int key, int value) { 
     if (mp.find(key)!=mp.end()){ 
      moveNode(mp[key]); 
      mp[key]->value = value; 
     }else{ 
      if (mp.size()==cp){ 
       mp.erase(head->key); 
       rmNode(head); 
      } 
      Node * node = new Node(key,value); 
      mp[key] = node; 
      insertNode(node); 
     } 
    } 
}; 
+0

+1爲偉大的答案。我可能錯過了一些東西,但是set方法沒有缺少對map [key]的刪除調用(當緩存達到其滿容量時)? – 2018-02-11 19:53:42

0

LRU頁面置換技術:

當一個頁面被引用,所需要的頁面可能是在緩存中。

If in the cache:我們需要將它放到緩存隊列的前面。

If NOT in the cache:我們把它放在緩存中。簡而言之,我們在緩存隊列的前面添加一個新頁面。如果緩存已滿,即所有幀都已滿,我們從緩存隊列的後面移除一個頁面,並將新頁面添加到緩存隊列的前面。

# Cache Size 
csize = int(input()) 

# Sequence of pages 
pages = list(map(int,input().split())) 

# Take a cache list 
cache=[] 

# Keep track of number of elements in cache 
n=0 

# Count Page Fault 
fault=0 

for page in pages: 
    # If page exists in cache 
    if page in cache: 
     # Move the page to front as it is most recent page 
     # First remove from cache and then append at front 
     cache.remove(page) 
     cache.append(page) 
    else: 
     # Cache is full 
     if(n==csize): 
      # Remove the least recent page 
      cache.pop(0) 
     else: 
      # Increment element count in cache 
      n=n+1 

     # Page not exist in cache => Page Fault 
     fault += 1 
     cache.append(page) 

print("Page Fault:",fault) 

輸入/輸出

Input: 
3 
1 2 3 4 1 2 5 1 2 3 4 5 

Output: 
Page Fault: 10