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