Home | History | Annotate | Download | only in containers
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // This file contains a template for a Most Recently Used cache that allows
      6 // constant-time access to items using a key, but easy identification of the
      7 // least-recently-used items for removal.  Each key can only be associated with
      8 // one payload item at a time.
      9 //
     10 // The key object will be stored twice, so it should support efficient copying.
     11 //
     12 // NOTE: While all operations are O(1), this code is written for
     13 // legibility rather than optimality. If future profiling identifies this as
     14 // a bottleneck, there is room for smaller values of 1 in the O(1). :]
     15 
     16 #ifndef BASE_CONTAINERS_MRU_CACHE_H_
     17 #define BASE_CONTAINERS_MRU_CACHE_H_
     18 
     19 #include <stddef.h>
     20 
     21 #include <algorithm>
     22 #include <functional>
     23 #include <list>
     24 #include <map>
     25 #include <unordered_map>
     26 #include <utility>
     27 
     28 #include "base/logging.h"
     29 #include "base/macros.h"
     30 
     31 namespace base {
     32 namespace trace_event {
     33 namespace internal {
     34 
     35 template <class MruCacheType>
     36 size_t DoEstimateMemoryUsageForMruCache(const MruCacheType&);
     37 
     38 }  // namespace internal
     39 }  // namespace trace_event
     40 
     41 // MRUCacheBase ----------------------------------------------------------------
     42 
     43 // This template is used to standardize map type containers that can be used
     44 // by MRUCacheBase. This level of indirection is necessary because of the way
     45 // that template template params and default template params interact.
     46 template <class KeyType, class ValueType, class CompareType>
     47 struct MRUCacheStandardMap {
     48   typedef std::map<KeyType, ValueType, CompareType> Type;
     49 };
     50 
     51 // Base class for the MRU cache specializations defined below.
     52 template <class KeyType,
     53           class PayloadType,
     54           class HashOrCompareType,
     55           template <typename, typename, typename> class MapType =
     56               MRUCacheStandardMap>
     57 class MRUCacheBase {
     58  public:
     59   // The payload of the list. This maintains a copy of the key so we can
     60   // efficiently delete things given an element of the list.
     61   typedef std::pair<KeyType, PayloadType> value_type;
     62 
     63  private:
     64   typedef std::list<value_type> PayloadList;
     65   typedef typename MapType<KeyType,
     66                            typename PayloadList::iterator,
     67                            HashOrCompareType>::Type KeyIndex;
     68 
     69  public:
     70   typedef typename PayloadList::size_type size_type;
     71 
     72   typedef typename PayloadList::iterator iterator;
     73   typedef typename PayloadList::const_iterator const_iterator;
     74   typedef typename PayloadList::reverse_iterator reverse_iterator;
     75   typedef typename PayloadList::const_reverse_iterator const_reverse_iterator;
     76 
     77   enum { NO_AUTO_EVICT = 0 };
     78 
     79   // The max_size is the size at which the cache will prune its members to when
     80   // a new item is inserted. If the caller wants to manager this itself (for
     81   // example, maybe it has special work to do when something is evicted), it
     82   // can pass NO_AUTO_EVICT to not restrict the cache size.
     83   explicit MRUCacheBase(size_type max_size) : max_size_(max_size) {}
     84 
     85   virtual ~MRUCacheBase() = default;
     86 
     87   size_type max_size() const { return max_size_; }
     88 
     89   // Inserts a payload item with the given key. If an existing item has
     90   // the same key, it is removed prior to insertion. An iterator indicating the
     91   // inserted item will be returned (this will always be the front of the list).
     92   //
     93   // The payload will be forwarded.
     94   template <typename Payload>
     95   iterator Put(const KeyType& key, Payload&& payload) {
     96     // Remove any existing payload with that key.
     97     typename KeyIndex::iterator index_iter = index_.find(key);
     98     if (index_iter != index_.end()) {
     99       // Erase the reference to it. The index reference will be replaced in the
    100       // code below.
    101       Erase(index_iter->second);
    102     } else if (max_size_ != NO_AUTO_EVICT) {
    103       // New item is being inserted which might make it larger than the maximum
    104       // size: kick the oldest thing out if necessary.
    105       ShrinkToSize(max_size_ - 1);
    106     }
    107 
    108     ordering_.emplace_front(key, std::forward<Payload>(payload));
    109     index_.emplace(key, ordering_.begin());
    110     return ordering_.begin();
    111   }
    112 
    113   // Retrieves the contents of the given key, or end() if not found. This method
    114   // has the side effect of moving the requested item to the front of the
    115   // recency list.
    116   iterator Get(const KeyType& key) {
    117     typename KeyIndex::iterator index_iter = index_.find(key);
    118     if (index_iter == index_.end())
    119       return end();
    120     typename PayloadList::iterator iter = index_iter->second;
    121 
    122     // Move the touched item to the front of the recency ordering.
    123     ordering_.splice(ordering_.begin(), ordering_, iter);
    124     return ordering_.begin();
    125   }
    126 
    127   // Retrieves the payload associated with a given key and returns it via
    128   // result without affecting the ordering (unlike Get).
    129   iterator Peek(const KeyType& key) {
    130     typename KeyIndex::const_iterator index_iter = index_.find(key);
    131     if (index_iter == index_.end())
    132       return end();
    133     return index_iter->second;
    134   }
    135 
    136   const_iterator Peek(const KeyType& key) const {
    137     typename KeyIndex::const_iterator index_iter = index_.find(key);
    138     if (index_iter == index_.end())
    139       return end();
    140     return index_iter->second;
    141   }
    142 
    143   // Exchanges the contents of |this| by the contents of the |other|.
    144   void Swap(MRUCacheBase& other) {
    145     ordering_.swap(other.ordering_);
    146     index_.swap(other.index_);
    147     std::swap(max_size_, other.max_size_);
    148   }
    149 
    150   // Erases the item referenced by the given iterator. An iterator to the item
    151   // following it will be returned. The iterator must be valid.
    152   iterator Erase(iterator pos) {
    153     index_.erase(pos->first);
    154     return ordering_.erase(pos);
    155   }
    156 
    157   // MRUCache entries are often processed in reverse order, so we add this
    158   // convenience function (not typically defined by STL containers).
    159   reverse_iterator Erase(reverse_iterator pos) {
    160     // We have to actually give it the incremented iterator to delete, since
    161     // the forward iterator that base() returns is actually one past the item
    162     // being iterated over.
    163     return reverse_iterator(Erase((++pos).base()));
    164   }
    165 
    166   // Shrinks the cache so it only holds |new_size| items. If |new_size| is
    167   // bigger or equal to the current number of items, this will do nothing.
    168   void ShrinkToSize(size_type new_size) {
    169     for (size_type i = size(); i > new_size; i--)
    170       Erase(rbegin());
    171   }
    172 
    173   // Deletes everything from the cache.
    174   void Clear() {
    175     index_.clear();
    176     ordering_.clear();
    177   }
    178 
    179   // Returns the number of elements in the cache.
    180   size_type size() const {
    181     // We don't use ordering_.size() for the return value because
    182     // (as a linked list) it can be O(n).
    183     DCHECK(index_.size() == ordering_.size());
    184     return index_.size();
    185   }
    186 
    187   // Allows iteration over the list. Forward iteration starts with the most
    188   // recent item and works backwards.
    189   //
    190   // Note that since these iterators are actually iterators over a list, you
    191   // can keep them as you insert or delete things (as long as you don't delete
    192   // the one you are pointing to) and they will still be valid.
    193   iterator begin() { return ordering_.begin(); }
    194   const_iterator begin() const { return ordering_.begin(); }
    195   iterator end() { return ordering_.end(); }
    196   const_iterator end() const { return ordering_.end(); }
    197 
    198   reverse_iterator rbegin() { return ordering_.rbegin(); }
    199   const_reverse_iterator rbegin() const { return ordering_.rbegin(); }
    200   reverse_iterator rend() { return ordering_.rend(); }
    201   const_reverse_iterator rend() const { return ordering_.rend(); }
    202 
    203   bool empty() const { return ordering_.empty(); }
    204 
    205  private:
    206   template <class MruCacheType>
    207   friend size_t trace_event::internal::DoEstimateMemoryUsageForMruCache(
    208       const MruCacheType&);
    209 
    210   PayloadList ordering_;
    211   KeyIndex index_;
    212 
    213   size_type max_size_;
    214 
    215   DISALLOW_COPY_AND_ASSIGN(MRUCacheBase);
    216 };
    217 
    218 // MRUCache --------------------------------------------------------------------
    219 
    220 // A container that does not do anything to free its data. Use this when storing
    221 // value types (as opposed to pointers) in the list.
    222 template <class KeyType,
    223           class PayloadType,
    224           class CompareType = std::less<KeyType>>
    225 class MRUCache : public MRUCacheBase<KeyType, PayloadType, CompareType> {
    226  private:
    227   using ParentType = MRUCacheBase<KeyType, PayloadType, CompareType>;
    228 
    229  public:
    230   // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
    231   explicit MRUCache(typename ParentType::size_type max_size)
    232       : ParentType(max_size) {}
    233   virtual ~MRUCache() = default;
    234 
    235  private:
    236   DISALLOW_COPY_AND_ASSIGN(MRUCache);
    237 };
    238 
    239 // HashingMRUCache ------------------------------------------------------------
    240 
    241 template <class KeyType, class ValueType, class HashType>
    242 struct MRUCacheHashMap {
    243   typedef std::unordered_map<KeyType, ValueType, HashType> Type;
    244 };
    245 
    246 // This class is similar to MRUCache, except that it uses std::unordered_map as
    247 // the map type instead of std::map. Note that your KeyType must be hashable to
    248 // use this cache or you need to provide a hashing class.
    249 template <class KeyType, class PayloadType, class HashType = std::hash<KeyType>>
    250 class HashingMRUCache
    251     : public MRUCacheBase<KeyType, PayloadType, HashType, MRUCacheHashMap> {
    252  private:
    253   using ParentType =
    254       MRUCacheBase<KeyType, PayloadType, HashType, MRUCacheHashMap>;
    255 
    256  public:
    257   // See MRUCacheBase, noting the possibility of using NO_AUTO_EVICT.
    258   explicit HashingMRUCache(typename ParentType::size_type max_size)
    259       : ParentType(max_size) {}
    260   virtual ~HashingMRUCache() = default;
    261 
    262  private:
    263   DISALLOW_COPY_AND_ASSIGN(HashingMRUCache);
    264 };
    265 
    266 }  // namespace base
    267 
    268 #endif  // BASE_CONTAINERS_MRU_CACHE_H_
    269