代码之家  ›  专栏  ›  技术社区  ›  user297850

LRU缓存设计

  •  63
  • user297850  · 技术社区  · 15 年前

    最近使用最少(LRU)缓存首先丢弃最近使用最少的项 如何设计和实现这种缓存类?设计要求如下:

    1)尽快找到物品

    2)一旦缓存未命中,并且缓存已满,我们需要尽快替换最近使用最少的项。

    如何从设计模式和算法设计的角度分析和实现这个问题?

    8 回复  |  直到 6 年前
        1
  •  92
  •   Aryabhatta    14 年前

    链接列表+指向链接列表节点的指针哈希表是实现LRU缓存的常用方法。这给O(1)个操作(假设一个合适的散列值)。这样做的好处(O(1)):您可以通过锁定整个结构来执行多线程版本。您不必担心粒度锁定等问题。

    简而言之,它的工作方式是:

    在访问值时,将链接列表中的相应节点移动到头部。

    当需要从缓存中移除值时,可以从尾部移除。

    向缓存添加值时,只需将其放在链接列表的头上。

    多亏了DouLip,这里是一个带有C++实现的站点: Miscellaneous Container Templates .

        2
  •  23
  •   Tsuneo Yoshioka    12 年前

    这是我对LRU缓存的简单示例C++实现,结合了哈希(unOrdEdMax)和列表。列表中的项具有访问映射的键,而映射中的项具有访问列表的列表迭代器。

    #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;
            };
    
    };
    
        3
  •  3
  •   Viren    10 年前

    下面是我对一个基本的、简单的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
  •  2
  •   Andrushenko Alexander    6 年前

    我在这里看到了一些不必要的复杂实现,所以我决定也提供我的实现。缓存只有get和set两种方法。希望它更易于阅读和理解:

    #include<unordered_map>
    #include<list>
    
    using namespace std;
    
    template<typename K, typename V = K>
    class LRUCache
    {
    
    private:
        list<K>items;
        unordered_map <K, pair<V, typename list<K>::iterator>> keyValuesMap;
        int csize;
    
    public:
        LRUCache(int s) :csize(s) {
            if (csize < 1)
                csize = 10;
        }
    
        void set(const K key, const V value) {
            auto pos = keyValuesMap.find(key);
            if (pos == keyValuesMap.end()) {
                items.push_front(key);
                keyValuesMap[key] = { value, items.begin() };
                if (keyValuesMap.size() > csize) {
                    keyValuesMap.erase(items.back());
                    items.pop_back();
                }
            }
            else {
                items.erase(pos->second.second);
                items.push_front(key);
                keyValuesMap[key] = { value, items.begin() };
            }
        }
    
        bool get(const K key, V &value) {
            auto pos = keyValuesMap.find(key);
            if (pos == keyValuesMap.end())
                return false;
            items.erase(pos->second.second);
            items.push_front(key);
            keyValuesMap[key] = { pos->second.first, items.begin() };
            value = pos->second.first;
            return true;
        }
    };
    
        5
  •  1
  •   burner    10 年前

    我有一个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"));
    
        6
  •  1
  •   Yang Liu    6 年前

    两年前我实现了一个线程安全的LRU缓存。

    lru通常使用hashmap和linkedlist实现。你可以用谷歌搜索实现细节。关于它有很多资源(维基百科也有很好的解释)。

    为了保证线程安全,每当修改LRU的状态时,都需要放置锁。

    我将粘贴我的C++代码在这里供大家参考。

    这是实现。

    /***
        A template thread-safe LRU container.
    
        Typically LRU cache is implemented using a doubly linked list and a hash map.
        Doubly Linked List is used to store list of pages with most recently used page
        at the start of the list. So, as more pages are added to the list,
        least recently used pages are moved to the end of the list with page
        at tail being the least recently used page in the list.
    
        Additionally, this LRU provides time-to-live feature. Each entry has an expiration
        datetime.
    ***/
    #ifndef LRU_CACHE_H
    #define LRU_CACHE_H
    
    #include <iostream>
    #include <list>
    
    #include <boost/unordered_map.hpp>
    #include <boost/shared_ptr.hpp>
    #include <boost/make_shared.hpp>
    #include <boost/date_time/posix_time/posix_time.hpp>
    #include <boost/thread/mutex.hpp>
    
    template <typename KeyType, typename ValueType>
      class LRUCache {
     private:
      typedef boost::posix_time::ptime DateTime;
    
      // Cache-entry
      struct ListItem {
      ListItem(const KeyType &key,
               const ValueType &value,
               const DateTime &expiration_datetime)
      : m_key(key), m_value(value), m_expiration_datetime(expiration_datetime){}
        KeyType m_key;
        ValueType m_value;
        DateTime m_expiration_datetime;
      };
    
      typedef boost::shared_ptr<ListItem> ListItemPtr;
      typedef std::list<ListItemPtr> LruList;
      typedef typename std::list<ListItemPtr>::iterator LruListPos;
      typedef boost::unordered_map<KeyType, LruListPos> LruMapper;
    
      // A mutext to ensuare thread-safety.
      boost::mutex m_cache_mutex;
    
      // Maximum number of entries.
      std::size_t m_capacity;
    
      // Stores cache-entries from latest to oldest.
      LruList m_list;
    
      // Mapper for key to list-position.
      LruMapper m_mapper;
    
      // Default time-to-live being add to entry every time we touch it.
      unsigned long m_ttl_in_seconds;
    
      /***
          Note : This is a helper function whose function call need to be wrapped
          within a lock. It returns true/false whether key exists and
          not expires. Delete the expired entry if necessary.
      ***/
      bool containsKeyHelper(const KeyType &key) {
        bool has_key(m_mapper.count(key) != 0);
        if (has_key) {
          LruListPos pos = m_mapper[key];
          ListItemPtr & cur_item_ptr = *pos;
    
          // Remove the entry if key expires
          if (isDateTimeExpired(cur_item_ptr->m_expiration_datetime)) {
            has_key = false;
            m_list.erase(pos);
            m_mapper.erase(key);
          }
        }
        return has_key;
      }
    
      /***
          Locate an item in list by key, and move it at the front of the list,
          which means make it the latest item.
          Note : This is a helper function whose function call need to be wrapped
          within a lock.
      ***/
      void makeEntryTheLatest(const KeyType &key) {
        if (m_mapper.count(key)) {
          // Add original item at the front of the list,
          // and update <Key, ListPosition> mapper.
          LruListPos original_list_position = m_mapper[key];
          const ListItemPtr & cur_item_ptr = *original_list_position;
          m_list.push_front(cur_item_ptr);
          m_mapper[key] = m_list.begin();
    
          // Don't forget to update its expiration datetime.
          m_list.front()->m_expiration_datetime = getExpirationDatetime(m_list.front()->m_expiration_datetime);
    
          // Erase the item at original position.
          m_list.erase(original_list_position);
        }
      }
    
     public:
    
      /***
          Cache should have capacity to limit its memory usage.
          We also add time-to-live for each cache entry to expire
          the stale information. By default, ttl is one hour.
      ***/
     LRUCache(std::size_t capacity, unsigned long ttl_in_seconds = 3600)
       : m_capacity(capacity), m_ttl_in_seconds(ttl_in_seconds) {}
    
      /***
          Return now + time-to-live
      ***/
      DateTime getExpirationDatetime(const DateTime &now) {
        static const boost::posix_time::seconds ttl(m_ttl_in_seconds);
        return now + ttl;
      }
    
      /***
          If input datetime is older than current datetime,
          then it is expired.
      ***/
      bool isDateTimeExpired(const DateTime &date_time) {
        return date_time < boost::posix_time::second_clock::local_time();
      }
    
      /***
          Return the number of entries in this cache.
       ***/
      std::size_t size() {
        boost::mutex::scoped_lock lock(m_cache_mutex);
        return m_mapper.size();
      }
    
      /***
          Get value by key.
          Return true/false whether key exists.
          If key exists, input paramter value will get updated.
      ***/
      bool get(const KeyType &key, ValueType &value) {
        boost::mutex::scoped_lock lock(m_cache_mutex);
        if (!containsKeyHelper(key)) {
          return false;
        } else {
          // Make the entry the latest and update its TTL.
          makeEntryTheLatest(key);
    
          // Then get its value.
          value = m_list.front()->m_value;
          return true;
        }
      }
    
      /***
          Add <key, value> pair if no such key exists.
          Otherwise, just update the value of old key.
      ***/
      void put(const KeyType &key, const ValueType &value) {
        boost::mutex::scoped_lock lock(m_cache_mutex);
        if (containsKeyHelper(key)) {
          // Make the entry the latest and update its TTL.
          makeEntryTheLatest(key);
    
          // Now we only need to update its value.
          m_list.front()->m_value = value;
        } else { // Key exists and is not expired.
          if (m_list.size() == m_capacity) {
            KeyType delete_key = m_list.back()->m_key;
            m_list.pop_back();
            m_mapper.erase(delete_key);
          }
    
          DateTime now = boost::posix_time::second_clock::local_time();
          m_list.push_front(boost::make_shared<ListItem>(key, value,
                                                         getExpirationDatetime(now)));
          m_mapper[key] = m_list.begin();
        }
      }
    };
    #endif
    

    这是单元测试。

    #include "cxx_unit.h"
    #include "lru_cache.h"
    
    struct LruCacheTest
      : public FDS::CxxUnit::TestFixture<LruCacheTest>{
      CXXUNIT_TEST_SUITE();
      CXXUNIT_TEST(LruCacheTest, testContainsKey);
      CXXUNIT_TEST(LruCacheTest, testGet);
      CXXUNIT_TEST(LruCacheTest, testPut);
      CXXUNIT_TEST_SUITE_END();
    
      void testContainsKey();
      void testGet();
      void testPut();
    };
    
    
    void LruCacheTest::testContainsKey() {
      LRUCache<int,std::string> cache(3);
      cache.put(1,"1"); // 1
      cache.put(2,"2"); // 2,1
      cache.put(3,"3"); // 3,2,1
      cache.put(4,"4"); // 4,3,2
    
      std::string value_holder("");
      CXXUNIT_ASSERT(cache.get(1, value_holder) == false); // 4,3,2
      CXXUNIT_ASSERT(value_holder == "");
    
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true); // 2,4,3
      CXXUNIT_ASSERT(value_holder == "2");
    
      cache.put(5,"5"); // 5, 2, 4
    
      CXXUNIT_ASSERT(cache.get(3, value_holder) == false); // 5, 2, 4
      CXXUNIT_ASSERT(value_holder == "2"); // value_holder is still "2"
    
      CXXUNIT_ASSERT(cache.get(4, value_holder) == true); // 4, 5, 2
      CXXUNIT_ASSERT(value_holder == "4");
    
      cache.put(2,"II"); // {2, "II"}, 4, 5
    
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true); // 2, 4, 5
      CXXUNIT_ASSERT(value_holder == "II");
    
      // Cache-entries : {2, "II"}, {4, "4"}, {5, "5"}
      CXXUNIT_ASSERT(cache.size() == 3);
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true);
      CXXUNIT_ASSERT(cache.get(4, value_holder) == true);
      CXXUNIT_ASSERT(cache.get(5, value_holder) == true);
    }
    
    void LruCacheTest::testGet() {
      LRUCache<int,std::string> cache(3);
      cache.put(1,"1"); // 1
      cache.put(2,"2"); // 2,1
      cache.put(3,"3"); // 3,2,1
      cache.put(4,"4"); // 4,3,2
    
      std::string value_holder("");
      CXXUNIT_ASSERT(cache.get(1, value_holder) == false); // 4,3,2
      CXXUNIT_ASSERT(value_holder == "");
    
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true); // 2,4,3
      CXXUNIT_ASSERT(value_holder == "2");
    
      cache.put(5,"5"); // 5,2,4
      CXXUNIT_ASSERT(cache.get(5, value_holder) == true); // 5,2,4
      CXXUNIT_ASSERT(value_holder == "5");
    
      CXXUNIT_ASSERT(cache.get(4, value_holder) == true); // 4, 5, 2
      CXXUNIT_ASSERT(value_holder == "4");
    
    
      cache.put(2,"II");
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true); // {2 : "II"}, 4, 5
      CXXUNIT_ASSERT(value_holder == "II");
    
      // Cache-entries : {2, "II"}, {4, "4"}, {5, "5"}
      CXXUNIT_ASSERT(cache.size() == 3);
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true);
      CXXUNIT_ASSERT(cache.get(4, value_holder) == true);
      CXXUNIT_ASSERT(cache.get(5, value_holder) == true);
    }
    
    void LruCacheTest::testPut() {
      LRUCache<int,std::string> cache(3);
      cache.put(1,"1"); // 1
      cache.put(2,"2"); // 2,1
      cache.put(3,"3"); // 3,2,1
      cache.put(4,"4"); // 4,3,2
      cache.put(5,"5"); // 5,4,3
    
      std::string value_holder("");
      CXXUNIT_ASSERT(cache.get(2, value_holder) == false); // 5,4,3
      CXXUNIT_ASSERT(value_holder == "");
    
      CXXUNIT_ASSERT(cache.get(4, value_holder) == true); // 4,5,3
      CXXUNIT_ASSERT(value_holder == "4");
    
      cache.put(2,"II");
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true); // II,4,5
      CXXUNIT_ASSERT(value_holder == "II");
    
      // Cache-entries : {2, "II"}, {4, "4"}, {5, "5"}
      CXXUNIT_ASSERT(cache.size() == 3);
      CXXUNIT_ASSERT(cache.get(2, value_holder) == true);
      CXXUNIT_ASSERT(cache.get(4, value_holder) == true);
      CXXUNIT_ASSERT(cache.get(5, value_holder) == true);
    }
    
    CXXUNIT_REGISTER_TEST(LruCacheTest);
    
        7
  •  0
  •   Jerry Ju    14 年前

    缓存是否是支持按键检索值的数据结构,如哈希表?lru意味着缓存有一定的大小限制,我们需要定期删除使用最少的条目。

    如果使用链接列表+指针哈希表实现,那么如何按键o(1)检索值?

    我将使用哈希表实现lru缓存,每个条目的值都是指向prev/next条目的值+指针。

    对于多线程访问,我更喜欢读写器锁(由于争用通常很快,所以最好通过自旋锁实现)进行监视。

        8
  •  0
  •   Astik Anand    7 年前

    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