• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
MRUCacheBase(size_type max_size)83   explicit MRUCacheBase(size_type max_size) : max_size_(max_size) {}
84 
85   virtual ~MRUCacheBase() = default;
86 
max_size()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>
Put(const KeyType & key,Payload && 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.
Get(const KeyType & key)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).
Peek(const KeyType & key)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 
Peek(const KeyType & key)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|.
Swap(MRUCacheBase & 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.
Erase(iterator pos)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).
Erase(reverse_iterator pos)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.
ShrinkToSize(size_type new_size)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.
Clear()174   void Clear() {
175     index_.clear();
176     ordering_.clear();
177   }
178 
179   // Returns the number of elements in the cache.
size()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.
begin()193   iterator begin() { return ordering_.begin(); }
begin()194   const_iterator begin() const { return ordering_.begin(); }
end()195   iterator end() { return ordering_.end(); }
end()196   const_iterator end() const { return ordering_.end(); }
197 
rbegin()198   reverse_iterator rbegin() { return ordering_.rbegin(); }
rbegin()199   const_reverse_iterator rbegin() const { return ordering_.rbegin(); }
rend()200   reverse_iterator rend() { return ordering_.rend(); }
rend()201   const_reverse_iterator rend() const { return ordering_.rend(); }
202 
empty()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.
MRUCache(typename ParentType::size_type max_size)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.
HashingMRUCache(typename ParentType::size_type max_size)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