HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
UT_LRUCache.h
Go to the documentation of this file.
1 /*
2  * PROPRIETARY INFORMATION. This software is proprietary to
3  * Side Effects Software Inc., and is not to be reproduced,
4  * transmitted, or disclosed in any way without written permission.
5  *
6  * NAME: UT_LRUCache.h ( UT Library, C++)
7  *
8  * COMMENTS:
9  * The LRU cache has a hash table for fast access and a link list for
10  * quick removal when the cache is full. Items are unique in the cache.
11  */
12 
13 #ifndef UT_LRU_CACHE_H
14 #define UT_LRU_CACHE_H
15 
16 #include "UT_Assert.h"
17 #include "UT_Function.h"
18 #include "UT_IteratorRange.h"
19 #include "UT_NonCopyable.h"
20 #include "UT_UniquePtr.h"
21 
22 #include <SYS/SYS_Types.h>
23 #include <SYS/SYS_TypeTraits.h>
24 
25 #include <list>
26 #include <type_traits>
27 #include <unordered_map>
28 
29 namespace UT
30 {
31  // A dummy implementation of UT_RWLock that does nothing.
32  class RWNullLock
33  {
34  public:
35  void readLock() {}
36  void writeLock() {}
37  void readUnlock() {}
38  void writeUnlock() {}
39  };
40 }
41 
42 /// A default helper function used by UT_LRUCache to determine the size of the
43 /// objects it stores to help prune the storage so that it doesn't exceed the
44 /// maximum given in the constructor.
45 template<typename V>
46 inline exint
47 UTlruGetItemSize(const V &)
48 {
49  static_assert(!std::is_pointer<V>::value,
50  "Pointer types need their own size function.");
51  return sizeof(V);
52 }
53 
54 
55 /// A default helper function used by UT_LRUCache to determine whether an
56 /// object is currently in use and so should not be deleted when the cache
57 /// gets pruned.
58 template<typename V>
59 inline bool
61 {
62  return false;
63 }
64 
65 
66 
67 template<typename K,
68  typename V,
69  exint(*SizeFunc)(const V &) = UTlruGetItemSize<V>,
70  bool(*InUseFunc)(const V &) = UTlruGetItemInUse<V>,
71  typename L=UT::RWNullLock>
72 class UT_LRUCache :
73  public UT_NonCopyable
74 {
75  using ValueType = std::pair<const K, V>;
76  using ValueList = std::list<ValueType> ;
77  using KeyIteratorMap = std::unordered_map<K, typename ValueList::iterator>;
78 
79  template<typename PROXIED, typename TYPE>
80  friend class iterator_base;
81 
82  ValueList myValueList;
83  KeyIteratorMap myKeyMap;
84  exint myMaxSize;
85  exint myCurrentSize;
87  UT_Function<bool(const V &)> myInUseFunc;
88  mutable UT_UniquePtr<L> myLock;
89 
90 protected:
91  /// This iterator is just a simple proxy around either the regular iterator
92  /// or the const_iterator on the internal list.
93  template<typename PROXIED, typename TYPE>
95  {
96  using proxied_iterator = PROXIED;
97 
98  public:
99  using iterator_category = std::bidirectional_iterator_tag;
100  using value_type = TYPE;
101  using difference_type = std::ptrdiff_t;
102  using pointer = TYPE*;
103  using reference = TYPE&;
104 
105  iterator_base() : myLock(nullptr) {}
106 
108  {
109  other.myLock->readLock();
110  myIt = other.myIt;
111  myLock = other.myLock;
112  }
113 
115  {
116  myIt = std::move(other.myIt);
117  other.myIt = proxied_iterator();
118  myLock = other.myLock;
119  other.myLock = nullptr;
120  }
121 
123  {
124  other.myLock->readLock();
125  myIt = other.myIt;
126  myLock = other.myLock;
127  return *this;
128  }
129 
131  {
132  myIt = std::move(other.myIt);
133  other.myIt = proxied_iterator();
134  myLock = other.myLock;
135  other.myLock = nullptr;
136  return *this;
137  }
138 
140  {
141  // Release the read lock on destroy.
142  if (myLock)
143  myLock->readUnlock();
144  }
145 
146  iterator_base &operator++() { ++myIt; return *this; }
147  iterator_base &operator--() { --myIt; return *this; }
148 
149  pointer operator->() const { return &(*myIt); }
150  reference operator*() const { return *myIt; }
151 
152  bool operator==(const iterator_base &other) const
153  {
154  return myIt == other.myIt;
155  }
156  bool operator!=(const iterator_base &other) const
157  {
158  return myIt != other.myIt;
159  }
160 
161  protected:
163 
164  iterator_base(proxied_iterator it, L *lock) :
165  myIt(it), myLock(lock) { }
166 
167  private:
168  proxied_iterator myIt;
169  L *myLock;
170  };
171 
172 public:
173  /// A iterator pointing to mutable values.
174  using iterator = iterator_base<typename ValueList::iterator, std::pair<const K, V>>;
175 
176  /// A const iterator pointing to immutable values.
177  using const_iterator = iterator_base<typename ValueList::const_iterator, const std::pair<const K, V>>;
178 
179  /// Construct a new LRU cache of a given @c max_size. By default the cache
180  /// is not thread-safe, but by setting the template argument @c L to
181  /// @c UT_RWLock, the cache is automatically thread-safe. This incurs some
182  /// overhead due to the locking, however, so only do that if absolutely
183  /// required.
185  myMaxSize(max_size),
186  myCurrentSize(0),
187  mySizeFunc(SizeFunc),
188  myInUseFunc(InUseFunc),
189  myLock(nullptr)
190  {
192  myLock = UTmakeUnique<L>();
193  }
194 
195  /// Sets the new maximum size for the cache. If the new maximum is smaller
196  /// than the current maximum, the cache will be pruned to fit the new
197  /// maximums size.
198  void setMaxSize(exint max_size)
199  {
200  if (max_size >= myMaxSize)
201  {
202  myMaxSize = max_size;
203  }
204  else
205  {
206  myMaxSize = max_size;
207  if (myLock)
208  myLock->writeLock();
209 
210  prune(nullptr);
211 
212  if (myLock)
213  myLock->writeUnlock();
214  }
215  }
216 
217  /// Returns the current maximum size of the cache.
218  exint maxSize() const { return myMaxSize; }
219 
220  /// Returns the current size of the cache, in arbitrary units.
221  /// If the cache is thread-safe this value may not be the most up-to-date.
223  {
224  if (myLock)
225  myLock->readLock();
226 
227  exint size = myCurrentSize;
228 
229  if (myLock)
230  myLock->readUnlock();
231  return size;
232  }
233 
234  /// Returns the number of items in the cache.
235  exint count() const
236  {
237  if (myLock)
238  myLock->readLock();
239 
240  exint size = myValueList.size();
241 
242  if (myLock)
243  myLock->readUnlock();
244  return size;
245  }
246 
247  /// Checks for the presence of the item with the given @c key. Does not
248  /// affect their ordering, like @c find.
249  bool contains(const K &key) const
250  {
251  if (myLock)
252  myLock->readLock();
253 
254  bool found = (myKeyMap.find(key) != myKeyMap.end());
255 
256  if (myLock)
257  myLock->readUnlock();
258  return found;
259  }
260 
261  /// Find the item in the cache with the given key. If nothing is found
262  /// then the iterator will point to the end.
263  /// There's no const version, since looking up the key modifies the LRU
264  /// cache's state and the iterator holds a read lock, if the cache is
265  /// thread-safe.
266  /// @note Since the iterator holds a lock, do
267  iterator find(const K &key)
268  {
269  if (myLock)
270  myLock->readLock();
271 
272  auto itK = myKeyMap.find(key);
273  if (itK == myKeyMap.end())
274  {
275  if (myLock)
276  myLock->readUnlock();
277  return iterator(myValueList.end(), nullptr);
278  }
279 
280  // Move this item to the top of the list.
281  if (itK->second != myValueList.begin())
282  {
283  myValueList.splice(myValueList.begin(), myValueList,
284  itK->second);
285  itK->second = myValueList.begin();
286  }
287 
288  return iterator(itK->second, myLock.get());
289  }
290 
291  /// Insert a new item into the cache. The item is automatically marked as
292  /// the most recently used. The cache is pruned beforehand, to avoid
293  /// evicting the item immediately. Returns a pair of an iterator and a
294  /// @c bool. The iterator points to the item inserted, or the existing item
295  /// if not evicted. The @c bool value indicates whether the item got
296  /// inserted or not.
297  std::pair<iterator, bool>
298  insert(const K &key, V &&value, bool evict=false)
299  {
300  if (myLock)
301  {
302  // Get both read and write locks. Upon exit, we relinquish the
303  // write lock, but the read lock gets carried out through the
304  // iterator.
305  myLock->writeLock();
306  myLock->readLock();
307  }
308 
309  std::pair<iterator, bool> result;
310  auto itK = myKeyMap.find(key);
311  if (itK != myKeyMap.end() && !evict)
312  {
313  // The item already exists and we don't want to evict the existing
314  // item.
315  // Note we should probably move this to the front of the
316  // LRU, there is a test for this disabled in the testut.
317  // In practice it likely is moot as usually people will
318  // have just done a find.
319  result = std::make_pair(iterator(itK->second,myLock.get()), false);
320  }
321  else
322  {
323  if (itK != myKeyMap.end())
324  {
325  // If we're replacing an item, take that away from the current
326  // size before updating it with the new value.
327  myCurrentSize -= mySizeFunc((itK->second)->second);
328  myCurrentSize += mySizeFunc(value);
329 
330  prune(&itK->second);
331 
332  // We're just updating an existing item. Move it to the
333  // beginning and move the contents of the existing object onto
334  // the old one.
335  myValueList.splice(myValueList.begin(), myValueList,
336  itK->second);
337 
338  myValueList.begin()->second = std::move(value);
339  itK->second = myValueList.begin();
340 
341  result = std::make_pair(
342  iterator(itK->second, myLock.get()), true);
343  }
344  else
345  {
346  myCurrentSize += mySizeFunc(value);
347 
348  prune(nullptr);
349 
350  myValueList.push_front(std::make_pair(key, std::move(value)));
351  myKeyMap.insert(std::make_pair(key, myValueList.begin()));
352 
353  result = std::make_pair(
354  iterator(myValueList.begin(), myLock.get()), true);
355  }
356  }
357 
358  if (myLock)
359  myLock->writeUnlock();
360 
361  return result;
362  }
363 
364 
365  /// Removes the item matching the @c key. If the item existed and was
366  /// successfully removed, then this function returns @c true.
367  bool erase(const K &key)
368  {
369  bool updated = false;
370  if (myLock)
371  myLock->writeLock();
372 
373  auto itK = myKeyMap.find(key);
374  if (itK != myKeyMap.end())
375  {
376  myCurrentSize -= mySizeFunc((itK->second)->second);
377  myValueList.erase(itK->second);
378  myKeyMap.erase(itK);
379  updated = true;
380  }
381 
382  if (myLock)
383  myLock->writeUnlock();
384 
385  return updated;
386  }
387 
388  /// Steals an item from the cache. This is functionally equivalent to
389  /// @c erase with a key, except the item in the cache also gets hoisted
390  /// outside into @c value, if it exists.
391  bool steal(const K &key, V &&value)
392  {
393  bool updated = false;
394  if (myLock)
395  myLock->writeLock();
396 
397  auto itK = myKeyMap.find(key);
398  if (itK != myKeyMap.end())
399  {
400  myCurrentSize -= mySizeFunc((itK->second)->second);
401 
402  // Move the contents of the cache item into the outside item.
403  value = std::move((itK->second)->second);
404 
405  myValueList.erase(itK->second);
406  myKeyMap.erase(itK);
407  updated = true;
408  }
409 
410  if (myLock)
411  myLock->writeUnlock();
412 
413  return updated;
414  }
415 
416  /// Clears the cache completely.
417  void clear()
418  {
419  if (myLock) myLock->writeLock();
420 
421  myKeyMap.clear();
422  myValueList.clear();
423  myCurrentSize = 0;
424 
425  if (myLock) myLock->writeUnlock();
426  }
427 
428  /// Returns an iterator to the front-most item in the LRU cache.
430  {
431  if (myLock) myLock->readLock();
432  return iterator(myValueList.begin(), myLock.get());
433  }
434 
435  /// Returns an iterator to the end of the LRU list.
437  {
438  // The iterator will unlock the read lock when it destroys.
439  return iterator(myValueList.end(), nullptr);
440  }
441 
442  /// Returns a const iterator to the front-most item in the LRU cache.
444  {
445  if (myLock) myLock->readLock();
446  return const_iterator(myValueList.begin(), myLock.get());
447  }
448 
449  /// Returns a const iterator to the end of the LRU list.
451  {
452  // The iterator will unlock the read lock when it destroys.
453  return const_iterator(myValueList.end(), nullptr);
454  }
455 
456 private:
457  /// Prune the list, from the last to the first. We skip objects that define
458  /// inUse() which returns @c true.
459  /// @note This function assumes that the caller holds the write lock.
460  void prune(typename ValueList::iterator *itSkipV = nullptr)
461  {
462  for (auto ritV = myValueList.rbegin();
463  myCurrentSize > myMaxSize && ritV != myValueList.rend(); ++ritV)
464  {
465  if ((itSkipV && ritV.base() == *itSkipV) ||
466  myInUseFunc(ritV->second))
467  {
468  continue;
469  }
470 
471  myCurrentSize -= mySizeFunc(ritV->second);
472 
473  auto itK = myKeyMap.find(ritV->first);
474  myKeyMap.erase(itK);
475  // Delicate dance because erase takes a forward iterator, but we're
476  // using a reverse iterator.
477  auto itV = myValueList.erase(std::prev(ritV.base()));
478  ritV = typename ValueList::reverse_iterator(itV);
479  }
480  }
481 };
482 
483 #endif
iterator_base(proxied_iterator it, L *lock)
Definition: UT_LRUCache.h:164
bool erase(const K &key)
Definition: UT_LRUCache.h:367
std::pair< iterator, bool > insert(const K &key, V &&value, bool evict=false)
Definition: UT_LRUCache.h:298
bool steal(const K &key, V &&value)
Definition: UT_LRUCache.h:391
GLsizei const GLfloat * value
Definition: glcorearb.h:824
int64 exint
Definition: SYS_Types.h:125
iterator find(const K &key)
Definition: UT_LRUCache.h:267
bool UTlruGetItemInUse(const V &)
Definition: UT_LRUCache.h:60
**But if you need a result
Definition: thread.h:622
#define SYS_EXINT_MAX
Definition: SYS_Types.h:181
bool operator!=(const iterator_base &other) const
Definition: UT_LRUCache.h:156
iterator_base< typename ValueList::iterator, std::pair< const cl_mem, ut_clBuffer >> iterator
A iterator pointing to mutable values.
Definition: UT_LRUCache.h:174
std::unique_ptr< T, Deleter > UT_UniquePtr
A smart pointer for unique ownership of dynamically allocated objects.
Definition: UT_UniquePtr.h:39
iterator_base< typename ValueList::const_iterator, const std::pair< const cl_mem, ut_clBuffer >> const_iterator
A const iterator pointing to immutable values.
Definition: UT_LRUCache.h:177
exint currentSize() const
Definition: UT_LRUCache.h:222
iterator begin()
Returns an iterator to the front-most item in the LRU cache.
Definition: UT_LRUCache.h:429
void readUnlock()
Definition: UT_LRUCache.h:37
iterator_base & operator=(iterator_base &&other)
Definition: UT_LRUCache.h:130
exint UTlruGetItemSize(const V &)
Definition: UT_LRUCache.h:47
iterator_base(iterator_base &&other)
Definition: UT_LRUCache.h:114
bool operator==(const iterator_base &other) const
Definition: UT_LRUCache.h:152
iterator end()
Returns an iterator to the end of the LRU list.
Definition: UT_LRUCache.h:436
void writeLock()
Definition: UT_LRUCache.h:36
pointer operator->() const
Definition: UT_LRUCache.h:149
std::function< T > UT_Function
Definition: UT_Function.h:37
exint maxSize() const
Returns the current maximum size of the cache.
Definition: UT_LRUCache.h:218
UT_LRUCache(exint max_size=SYS_EXINT_MAX)
Definition: UT_LRUCache.h:184
void setMaxSize(exint max_size)
Definition: UT_LRUCache.h:198
bool contains(const K &key) const
Definition: UT_LRUCache.h:249
GLsizeiptr size
Definition: glcorearb.h:664
const_iterator begin() const
Returns a const iterator to the front-most item in the LRU cache.
Definition: UT_LRUCache.h:443
const_iterator end() const
Returns a const iterator to the end of the LRU list.
Definition: UT_LRUCache.h:450
exint count() const
Returns the number of items in the cache.
Definition: UT_LRUCache.h:235
iterator_base(const iterator_base &other)
Definition: UT_LRUCache.h:107
iterator_base & operator=(const iterator_base &other)
Definition: UT_LRUCache.h:122
std::bidirectional_iterator_tag iterator_category
Definition: UT_LRUCache.h:99
void prune(TreeT &tree, typename TreeT::ValueType tolerance=zeroVal< typename TreeT::ValueType >(), bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing with tiles any nodes whose values are all the same...
Definition: Prune.h:335
reference operator*() const
Definition: UT_LRUCache.h:150
friend class iterator_base
Definition: UT_LRUCache.h:80
void readLock()
Definition: UT_LRUCache.h:35
iterator_base & operator++()
Definition: UT_LRUCache.h:146
void clear()
Clears the cache completely.
Definition: UT_LRUCache.h:417
iterator_base & operator--()
Definition: UT_LRUCache.h:147
void writeUnlock()
Definition: UT_LRUCache.h:38
std::ptrdiff_t difference_type
Definition: UT_LRUCache.h:101