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