Ben Clayton | ac43aa7 | 2020-04-04 00:48:13 +0100 | [diff] [blame] | 1 | // Copyright 2020 The SwiftShader Authors. All Rights Reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #ifndef sw_LRUCache_hpp |
| 16 | #define sw_LRUCache_hpp |
| 17 | |
| 18 | #include "System/Debug.hpp" |
| 19 | |
| 20 | #include <cstddef> |
| 21 | #include <functional> |
| 22 | #include <unordered_set> |
| 23 | #include <vector> |
| 24 | |
| 25 | namespace sw { |
| 26 | |
| 27 | // LRUCache is a least recently used cache of a fixed capacity. |
| 28 | template<typename KEY, typename DATA, typename HASH = std::hash<KEY> > |
| 29 | class LRUCache |
| 30 | { |
| 31 | struct Entry; |
| 32 | |
| 33 | public: |
| 34 | using Key = KEY; |
| 35 | using Data = DATA; |
| 36 | using Hash = HASH; |
| 37 | |
| 38 | // view is a const accessor of a single cache entry. |
| 39 | class view |
| 40 | { |
| 41 | public: |
| 42 | inline view(Entry *); |
| 43 | |
| 44 | inline const Key &key() const; |
| 45 | inline const Data &data() const; |
| 46 | |
| 47 | private: |
| 48 | Entry *entry; |
| 49 | }; |
| 50 | |
| 51 | class iterator |
| 52 | { |
| 53 | public: |
| 54 | inline iterator(Entry *); |
| 55 | inline view operator*() const; |
| 56 | inline iterator &operator++(); |
| 57 | inline bool operator==(const iterator &) const; |
| 58 | inline bool operator!=(const iterator &) const; |
| 59 | |
| 60 | private: |
| 61 | Entry *entry; |
| 62 | }; |
| 63 | |
| 64 | // Construct a LRU cache with the given maximum number of entries. |
| 65 | inline LRUCache(size_t capacity); |
| 66 | inline ~LRUCache() = default; |
| 67 | |
| 68 | // lookup() looks up the cache entry with the given key. |
| 69 | // If the entry is found, this is moved to the most-recent position in the |
| 70 | // cache, and its data is returned. |
| 71 | // If the entry is not found, then a default initialized Data is returned. |
| 72 | inline Data lookup(const Key &key); |
| 73 | |
| 74 | // add() adds the data to the cache using the given key, placed at the |
| 75 | // most-recent position in the cache. |
| 76 | // If an existing entry exists in the cache with the given key, then this is |
| 77 | // replaced with data. |
| 78 | // If no existing entry exists in the cache, and the cache is already full |
| 79 | // then the least recently used entry is evicted before adding the new |
| 80 | // entry. |
| 81 | inline void add(const Key &key, const Data &data); |
| 82 | |
| 83 | // clear() clears the cache of all elements. |
| 84 | inline void clear(); |
| 85 | |
| 86 | // Range based iterators. |
| 87 | inline iterator begin() const; |
| 88 | inline iterator end() const; |
| 89 | |
| 90 | private: |
| 91 | LRUCache(const LRUCache &) = delete; |
| 92 | LRUCache(LRUCache &&) = delete; |
| 93 | LRUCache &operator=(const LRUCache &) = delete; |
| 94 | LRUCache &operator=(LRUCache &&) = delete; |
| 95 | |
| 96 | //Keyed holds a key. See find() for more information. |
| 97 | struct Keyed |
| 98 | { |
| 99 | Key key = {}; |
| 100 | }; |
| 101 | |
| 102 | // Cache entry structure. |
| 103 | // Holds the unique copy of the key and data, along with pointers for |
| 104 | // maintaining the linked list. |
| 105 | struct Entry : Keyed |
| 106 | { |
| 107 | Data data = {}; |
| 108 | Entry *next = nullptr; |
| 109 | Entry *prev = nullptr; |
| 110 | }; |
| 111 | |
| 112 | // KeyedComparator is a custom hasher and equality helper for Keyed. |
| 113 | struct KeyedComparator |
| 114 | { |
| 115 | // Hash function. |
| 116 | inline uint64_t operator()(const Keyed *k) const; |
| 117 | |
| 118 | // Equality function. |
| 119 | inline uint64_t operator()(const Keyed *a, const Keyed *b) const; |
| 120 | }; |
| 121 | |
| 122 | // find() returns the Entry* for the given key, or nullptr. |
| 123 | // find() performs this by casting the Key pointer to a Keyed pointer for |
| 124 | // searching the std::unordered_set. |
| 125 | // |
| 126 | // This is to avoid having a duplicate Key held by both an |
| 127 | // unordered_map<Key, Entry*> and the Entry itself, as the Key type may be |
| 128 | // large. |
| 129 | // |
| 130 | // While we could use an unordered_set<Entry*>, this then requires the |
| 131 | // construction of a temporary Entry to perform the call to |
| 132 | // unordered_set<Entry*>::find(). This is undesirable as the Data type might |
| 133 | // be large or have an expensive constructor. |
| 134 | // |
| 135 | // C++20 gains a new templated overload for unordered_set<Entry*>::find() |
| 136 | // which would allow us to use unordered_set<Entry*>::find(Key*). |
| 137 | // Until we can use C++20, keep this casting nastiness hidden away in this |
| 138 | // one function. |
| 139 | inline Entry *find(const Key &key); |
| 140 | |
| 141 | // unlinks the entry from the list it is linked in. |
| 142 | inline void unlink(Entry *); |
| 143 | |
| 144 | // links the entry to the head of the LRU. |
| 145 | inline void link(Entry *); |
| 146 | |
| 147 | // storage holds the allocations of all the entries. |
| 148 | // This vector must not change size for the lifetime of the cache. |
| 149 | std::vector<Entry> storage; |
| 150 | |
| 151 | // set is an unordered set of Keyed*, using the KeyedComparator for hash and |
| 152 | // equality testing. |
| 153 | std::unordered_set<const Keyed *, KeyedComparator, KeyedComparator> set; |
| 154 | |
| 155 | Entry *free = nullptr; // Singly-linked (prev is nullptr) list of free entries. |
| 156 | Entry *head = nullptr; // Pointer to the most recently used entry in the LRU. |
| 157 | Entry *tail = nullptr; // Pointer to the least recently used entry in the LRU. |
| 158 | }; |
| 159 | |
| 160 | //////////////////////////////////////////////////////////////////////////////// |
| 161 | // LRUCache<>::view |
| 162 | //////////////////////////////////////////////////////////////////////////////// |
| 163 | template<typename KEY, typename DATA, typename HASH> |
| 164 | LRUCache<KEY, DATA, HASH>::view::view(Entry *entry) |
| 165 | : entry(entry) |
| 166 | {} |
| 167 | |
| 168 | template<typename KEY, typename DATA, typename HASH> |
| 169 | const KEY &LRUCache<KEY, DATA, HASH>::view::key() const |
| 170 | { |
| 171 | return entry->key; |
| 172 | } |
| 173 | |
| 174 | template<typename KEY, typename DATA, typename HASH> |
| 175 | const DATA &LRUCache<KEY, DATA, HASH>::view::data() const |
| 176 | { |
| 177 | return entry->data; |
| 178 | } |
| 179 | |
| 180 | //////////////////////////////////////////////////////////////////////////////// |
| 181 | // LRUCache<>::iterator |
| 182 | //////////////////////////////////////////////////////////////////////////////// |
| 183 | template<typename KEY, typename DATA, typename HASH> |
| 184 | LRUCache<KEY, DATA, HASH>::iterator::iterator(Entry *entry) |
| 185 | : entry(entry) |
| 186 | {} |
| 187 | |
| 188 | template<typename KEY, typename DATA, typename HASH> |
| 189 | typename LRUCache<KEY, DATA, HASH>::view LRUCache<KEY, DATA, HASH>::iterator::operator*() const |
| 190 | { |
| 191 | return view{ entry }; |
| 192 | } |
| 193 | |
| 194 | template<typename KEY, typename DATA, typename HASH> |
| 195 | typename LRUCache<KEY, DATA, HASH>::iterator &LRUCache<KEY, DATA, HASH>::iterator::operator++() |
| 196 | { |
| 197 | entry = entry->next; |
| 198 | return *this; |
| 199 | } |
| 200 | |
| 201 | template<typename KEY, typename DATA, typename HASH> |
| 202 | bool LRUCache<KEY, DATA, HASH>::iterator::operator==(const iterator &rhs) const |
| 203 | { |
| 204 | return entry == rhs.entry; |
| 205 | } |
| 206 | |
| 207 | template<typename KEY, typename DATA, typename HASH> |
| 208 | bool LRUCache<KEY, DATA, HASH>::iterator::operator!=(const iterator &rhs) const |
| 209 | { |
| 210 | return entry != rhs.entry; |
| 211 | } |
| 212 | |
| 213 | //////////////////////////////////////////////////////////////////////////////// |
| 214 | // LRUCache<> |
| 215 | //////////////////////////////////////////////////////////////////////////////// |
| 216 | template<typename KEY, typename DATA, typename HASH> |
| 217 | LRUCache<KEY, DATA, HASH>::LRUCache(size_t capacity) |
| 218 | : storage(capacity) |
| 219 | { |
| 220 | for(size_t i = 0; i < capacity; i++) |
| 221 | { |
| 222 | Entry *entry = &storage[i]; |
| 223 | entry->next = free; // No need for back link here. |
| 224 | free = entry; |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | template<typename KEY, typename DATA, typename HASH> |
| 229 | DATA LRUCache<KEY, DATA, HASH>::lookup(const Key &key) |
| 230 | { |
| 231 | if(Entry *entry = find(key)) |
| 232 | { |
| 233 | unlink(entry); |
| 234 | link(entry); |
| 235 | return entry->data; |
| 236 | } |
| 237 | return {}; |
| 238 | } |
| 239 | |
| 240 | template<typename KEY, typename DATA, typename HASH> |
| 241 | void LRUCache<KEY, DATA, HASH>::add(const Key &key, const Data &data) |
| 242 | { |
| 243 | if(Entry *entry = find(key)) |
| 244 | { |
| 245 | // Move entry to front. |
| 246 | unlink(entry); |
| 247 | link(entry); |
| 248 | entry->data = data; |
| 249 | return; |
| 250 | } |
| 251 | |
| 252 | Entry *entry = free; |
| 253 | if(entry) |
| 254 | { |
| 255 | // Unlink from free. |
| 256 | free = entry->next; |
| 257 | entry->next = nullptr; |
| 258 | } |
| 259 | else |
| 260 | { |
| 261 | // Unlink least recently used. |
| 262 | entry = tail; |
| 263 | unlink(entry); |
| 264 | set.erase(entry); |
| 265 | } |
| 266 | |
| 267 | // link as most recently used. |
| 268 | link(entry); |
| 269 | if(tail == nullptr) |
| 270 | { |
| 271 | tail = entry; |
| 272 | } |
| 273 | |
| 274 | entry->key = key; |
| 275 | entry->data = data; |
| 276 | set.emplace(entry); |
| 277 | } |
| 278 | |
| 279 | template<typename KEY, typename DATA, typename HASH> |
| 280 | void LRUCache<KEY, DATA, HASH>::clear() |
| 281 | { |
| 282 | while(Entry *entry = head) |
| 283 | { |
| 284 | unlink(entry); |
| 285 | entry->next = free; // No need for back link here. |
| 286 | free = entry; |
| 287 | } |
| 288 | set.clear(); |
| 289 | } |
| 290 | |
| 291 | template<typename KEY, typename DATA, typename HASH> |
| 292 | typename LRUCache<KEY, DATA, HASH>::iterator LRUCache<KEY, DATA, HASH>::begin() const |
| 293 | { |
| 294 | return { head }; |
| 295 | } |
| 296 | |
| 297 | template<typename KEY, typename DATA, typename HASH> |
| 298 | typename LRUCache<KEY, DATA, HASH>::iterator LRUCache<KEY, DATA, HASH>::end() const |
| 299 | { |
| 300 | return { nullptr }; |
| 301 | } |
| 302 | |
| 303 | template<typename KEY, typename DATA, typename HASH> |
| 304 | void LRUCache<KEY, DATA, HASH>::unlink(Entry *entry) |
| 305 | { |
| 306 | if(head == entry) { head = entry->next; } |
| 307 | if(tail == entry) { tail = entry->prev; } |
| 308 | if(entry->prev) { entry->prev->next = entry->next; } |
| 309 | if(entry->next) { entry->next->prev = entry->prev; } |
| 310 | entry->prev = nullptr; |
| 311 | entry->next = nullptr; |
| 312 | } |
| 313 | |
| 314 | template<typename KEY, typename DATA, typename HASH> |
| 315 | void LRUCache<KEY, DATA, HASH>::link(Entry *entry) |
| 316 | { |
| 317 | ASSERT_MSG(entry->next == nullptr, "link() called on entry already linked"); |
| 318 | ASSERT_MSG(entry->prev == nullptr, "link() called on entry already linked"); |
| 319 | if(head) |
| 320 | { |
| 321 | entry->next = head; |
| 322 | head->prev = entry; |
| 323 | } |
| 324 | head = entry; |
| 325 | if(!tail) { tail = entry; } |
| 326 | } |
| 327 | |
| 328 | template<typename KEY, typename DATA, typename HASH> |
| 329 | typename LRUCache<KEY, DATA, HASH>::Entry *LRUCache<KEY, DATA, HASH>::find(const Key &key) |
| 330 | { |
| 331 | auto asKeyed = reinterpret_cast<const Keyed *>(&key); |
| 332 | auto it = set.find(asKeyed); |
| 333 | if(it == set.end()) |
| 334 | { |
| 335 | return nullptr; |
| 336 | } |
| 337 | return const_cast<Entry *>(static_cast<const Entry *>(*it)); |
| 338 | } |
| 339 | |
| 340 | //////////////////////////////////////////////////////////////////////////////// |
| 341 | // LRUCache<>::KeyedComparator |
| 342 | //////////////////////////////////////////////////////////////////////////////// |
| 343 | template<typename KEY, typename DATA, typename HASH> |
| 344 | uint64_t LRUCache<KEY, DATA, HASH>::KeyedComparator::operator()(const Keyed *k) const |
| 345 | { |
| 346 | return Hash()(k->key); |
| 347 | } |
| 348 | |
| 349 | template<typename KEY, typename DATA, typename HASH> |
| 350 | uint64_t LRUCache<KEY, DATA, HASH>::KeyedComparator::operator()(const Keyed *a, const Keyed *b) const |
| 351 | { |
| 352 | return a->key == b->key; |
| 353 | } |
| 354 | |
| 355 | } // namespace sw |
| 356 | |
| 357 | #endif // sw_LRUCache_hpp |