olp-cpp-sdk  1.22.0
LruCache.h
1 /*
2  * Copyright (C) 2019-2021 HERE Europe B.V.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  * License-Filename: LICENSE
18  */
19 
20 #pragma once
21 
22 #include <cassert>
23 #include <cstddef>
24 #include <functional>
25 #include <map>
26 #include <utility>
27 
28 #include <olp/core/porting/try_emplace.h>
29 
30 namespace olp {
31 namespace utils {
40 template <typename T>
41 struct CacheCost {
43  std::size_t operator()(const T&) const { return 1; }
44 };
45 
65 template <typename Key, typename Value,
66  typename CacheCostFunc = CacheCost<Value>,
67  typename Compare = std::less<Key>,
68  template <typename> class Alloc = std::allocator>
69 class LruCache {
70  struct Bucket;
71  using MapType =
72  std::map<Key, Bucket, Compare, Alloc<std::pair<const Key, Bucket> > >;
73 
74  public:
76  using EvictionFunction = std::function<void(const Key&, Value&&)>;
77 
79  using CompareType = typename MapType::key_compare;
80 
82  using AllocType = typename MapType::allocator_type;
83 
89  class ValueType {
90  public:
96  inline const Key& key() const;
97 
103  inline const Value& value() const;
104 
105  protected:
107  typename MapType::const_iterator m_it;
108  };
109 
111  class const_iterator : public ValueType {
112  public:
114  typedef std::bidirectional_iterator_tag iterator_category;
116  typedef std::ptrdiff_t difference_type;
120  typedef const value_type& reference;
122  typedef const value_type* pointer;
123 
125  const_iterator() = default;
127  const_iterator(const const_iterator&) = default;
134 
143  inline bool operator==(const const_iterator& other) const;
152  inline bool operator!=(const const_iterator& other) const {
153  return !operator==(other);
154  }
155 
161  inline const_iterator& operator++();
167  inline const_iterator operator++(int);
168 
174  inline const_iterator& operator--();
180  inline const_iterator operator--(int);
181 
187  reference operator*() const { return *this; }
188 
194  pointer operator->() const { return this; }
195 
196  private:
197  template <typename, typename, typename, typename, template <typename> class>
198  friend class LruCache;
199 
200  explicit const_iterator(const typename MapType::const_iterator& it) {
201  this->m_it = it;
202  }
203 
204  explicit const_iterator(const typename MapType::iterator& it) {
205  this->m_it = it;
206  }
207  };
208 
217  explicit LruCache(const AllocType& alloc = AllocType())
218  : map_(alloc),
219  first_(map_.end()),
220  last_(map_.end()),
221  max_size_(0),
222  size_(0) {}
223 
233  LruCache(std::size_t maxSize, CacheCostFunc cacheCostFunc = CacheCostFunc(),
234  const CompareType& compare = CompareType(),
235  const AllocType& alloc = AllocType())
236  : cache_cost_func_(std::move(cacheCostFunc)),
237  map_(compare, alloc),
238  first_(map_.end()),
239  last_(map_.end()),
240  max_size_(maxSize),
241  size_(0) {}
242 
244  LruCache(const LruCache&) = delete;
245 
247  LruCache(LruCache&& other) noexcept
248  : eviction_callback_(std::move(other.eviction_callback_)),
249  cache_cost_func_(std::move(other.cache_cost_func_)),
250  map_(std::move(other.map_)),
251  first_(other.first_ != map_.end() ? map_.find(other.first_->first)
252  : map_.end()),
253  last_(other.last_ != map_.end() ? map_.find(other.last_->first)
254  : map_.end()),
255  max_size_(other.max_size_),
256  size_(other.size_) {}
257 
259  LruCache& operator=(const LruCache&) = delete;
260 
262  LruCache& operator=(LruCache&& other) noexcept {
263  eviction_callback_ = std::move(other.eviction_callback_);
264  cache_cost_func_ = std::move(other.cache_cost_func_);
265  map_ = std::move(other.map_);
266  first_ = other.first_ != map_.end() ? map_.find(other.first_->first)
267  : map_.end();
268  last_ =
269  other.last_ != map_.end() ? map_.find(other.last_->first) : map_.end();
270  std::swap(max_size_, other.max_size_);
271  std::swap(size_, other.size_);
272 
273  return *this;
274  }
275 
298  template <typename _Key, typename _Value>
299  std::pair<const_iterator, bool> Insert(_Key&& key, _Value&& value);
300 
316  template <typename _Value>
317  std::pair<const_iterator, bool> InsertOrAssign(Key key, _Value&& value);
318 
327  bool Erase(const Key& key) {
328  auto it = map_.find(key);
329  if (it == map_.end())
330  return false;
331 
332  Erase(it, false);
333  return true;
334  }
335 
344  auto prev = it++;
345 
346  Erase(prev->key());
347 
348  return it;
349  }
350 
356  std::size_t Size() const { return size_; }
357 
363  std::size_t GetMaxSize() const { return max_size_; }
364 
373  void Resize(size_t maxSize) {
374  max_size_ = maxSize;
375  evict();
376  }
377 
388  const_iterator Find(const Key& key) {
389  auto it = map_.find(key);
390  if (it != map_.end())
391  Promote(it);
392  return const_iterator{it};
393  }
394 
405  const_iterator FindNoPromote(const Key& key) const {
406  auto it = map_.find(key);
407  return const_iterator{it};
408  }
409 
420  const Value& Find(const Key& key, const Value& nullValue) {
421  auto it = Find(key);
422  return it == end() ? nullValue : it.value();
423  }
424 
426  const_iterator begin() const { return const_iterator{first_}; }
427 
429  const_iterator end() const { return const_iterator{map_.end()}; }
430 
432  const_iterator rbegin() const { return const_iterator{last_}; }
433 
435  const_iterator rend() const { return const_iterator{map_.end()}; }
436 
443  void Clear() {
444  map_.clear();
445  first_ = last_ = map_.end();
446  size_ = 0u;
447  }
448 
462  eviction_callback_ = std::move(func);
463  }
464 
465  private:
466  friend struct CacheLruChecker; // for unit-tests
467 
468  EvictionFunction eviction_callback_;
469  CacheCostFunc cache_cost_func_;
470  MapType map_;
471  typename MapType::iterator first_;
472  typename MapType::iterator last_;
473  std::size_t max_size_;
474  std::size_t size_;
475 
476  // Checks whether two keys are equal using only the comparison
477  // operator.
478  inline bool IsEqual(const Key& key1, const Key& key2) {
479  auto comp = map_.key_comp();
480  return !comp(key1, key2) && !comp(key2, key1);
481  }
482 
483  // The bucket that contains the value and the pointer to the previous
484  // and next items in the LRU chain.
485  struct Bucket {
486  using Iterator = typename MapType::iterator;
487 
488  // note - these constructors are only here because MSVC2013 doesn't
489  // auto-generate them as the standard mandates
490 #if defined(_MSC_VER) && _MSC_VER < 1900
491  inline Bucket(Iterator next, Iterator previous, Value&& value)
492  : next_(std::move(next)),
493  previous_(std::move(previous)),
494  value_(std::move(value)) {}
495  inline Bucket(Iterator next, Iterator previous, const Value& value)
496  : next_(std::move(next)),
497  previous_(std::move(previous)),
498  value_(value) {}
499  inline Bucket(const Bucket&) = delete;
500  inline Bucket(Bucket&& other)
501  : next_(std::move(other.next_)),
502  previous_(std::move(other.previous_)),
503  value_(std::move(other.value_)) {}
504 #endif
505 
506  Iterator next_;
507  Iterator previous_;
508  Value value_;
509 
510  inline void setNext(Iterator it) { next_ = std::move(it); }
511 
512  inline void setPrevious(Iterator it) { previous_ = std::move(it); }
513  };
514 
515  void Erase(typename MapType::iterator it, bool);
516  void AddInternal(const typename MapType::iterator& it, std::size_t cost,
517  std::size_t* oldCost = nullptr);
518  void Promote(const typename MapType::iterator& it);
519  void PopLast();
520  void evict() {
521  while (size_ > max_size_)
522  PopLast();
523  }
524 };
525 
526 template <typename Key, typename Value, typename CacheCostFunc,
527  typename Compare, template <typename> class Alloc>
528 template <typename _Key, typename _Value>
529 
553  _Value&& value)
554  -> std::pair<const_iterator, bool> {
555  Bucket bucket{map_.end(), map_.end(), std::forward<_Value>(value)};
556 
557  // If the item is too large, do not insert it.
558  std::size_t valueCost = cache_cost_func_(bucket.value_);
559  if (valueCost > max_size_)
560  return std::make_pair(const_iterator{end()}, false);
561 
562  auto it =
563  porting::try_emplace(map_, std::forward<_Key>(key), std::move(bucket));
564 
565  if (it.second) {
566  AddInternal(it.first, valueCost);
567  return std::make_pair(const_iterator{it.first}, true);
568  }
569  Promote(it.first);
570  return std::make_pair(const_iterator{it.first}, false);
571 }
572 
573 template <typename Key, typename Value, typename CacheCostFunc,
574  typename Compare, template <typename> class Alloc>
575 template <typename _Value>
576 
593  Key key, _Value&& value) -> std::pair<const_iterator, bool> {
594  // as we need "key" with the right type a couple of times, create a temp
595  // copy to prevent implicit conversions below
596  auto it = map_.lower_bound(key);
597  if (it != map_.end() && IsEqual(key, it->first)) {
598  // element already exists, update it
599  std::size_t oldCost = cache_cost_func_(it->second.value_);
600  it->second.value_ = std::forward<_Value>(value);
601  std::size_t newCost = cache_cost_func_(it->second.value_);
602  AddInternal(it, newCost, &oldCost);
603  return std::make_pair(const_iterator{it}, false);
604  } else {
605  // element doesn't exist, insert it
606  Bucket bucket{map_.end(), map_.end(), std::forward<_Value>(value)};
607  std::size_t newCost = cache_cost_func_(bucket.value_);
608  if (newCost > max_size_)
609  return std::make_pair(const_iterator{end()}, false);
610 
611  auto new_it =
612  map_.insert(it, std::make_pair(std::move(key), std::move(bucket)));
613  AddInternal(new_it, newCost);
614  return std::make_pair(const_iterator{new_it}, true);
615  }
616 }
617 
618 template <typename Key, typename Value, typename CacheCostFunc,
619  typename Compare, template <typename> class Alloc>
621  const typename MapType::iterator& it) {
622  if (it == first_)
623  return; // nothing to do
624 
625  // re-link previous and next buckets together
626  it->second.previous_->second.setNext(it->second.next_);
627  if (it->second.next_ != map_.end())
628  it->second.next_->second.setPrevious(it->second.previous_);
629  else
630  last_ = it->second.previous_;
631 
632  // re-link our bucket
633  it->second.setPrevious(map_.end());
634  it->second.setNext(first_);
635 
636  // update current head to ourselves
637  first_->second.setPrevious(it);
638  first_ = it;
639 }
640 
641 template <typename Key, typename Value, typename CacheCostFunc,
642  typename Compare, template <typename> class Alloc>
643 void LruCache<Key, Value, CacheCostFunc, Compare, Alloc>::AddInternal(
644  const typename MapType::iterator& it, std::size_t cost,
645  std::size_t* oldCost) {
646  if (!oldCost) {
647  // new bucket added
648  if (map_.size() == 1) {
649  // we're the first and only one.
650  assert(last_ == map_.end() && first_ == map_.end());
651  last_ = first_ = it;
652  } else {
653  // relink first bucket
654  it->second.setNext(first_);
655  first_->second.setPrevious(it);
656  first_ = it;
657  }
658  size_ += cost;
659  } else {
660  // key already in map - only promote
661  size_ += cost - *oldCost;
662  Promote(it);
663  }
664 
665  evict();
666 }
667 
668 template <typename Key, typename Value, typename CacheCostFunc,
669  typename Compare, template <typename> class Alloc>
671  typename MapType::iterator it, bool doEvictionCallback) {
672  std::size_t cost = cache_cost_func_(it->second.value_);
673 
674  if (it->second.next_ == map_.end())
675  last_ = it->second.previous_;
676  else
677  it->second.next_->second.setPrevious(it->second.previous_);
678 
679  if (it->second.previous_ == map_.end())
680  first_ = it->second.next_;
681  else
682  it->second.previous_->second.setNext(it->second.next_);
683 
684  if (doEvictionCallback && eviction_callback_)
685  eviction_callback_(it->first, std::move(it->second.value_));
686 
687  map_.erase(it);
688  size_ -= cost;
689 }
690 
691 template <typename Key, typename Value, typename CacheCostFunc,
692  typename Compare, template <typename> class Alloc>
693 void LruCache<Key, Value, CacheCostFunc, Compare, Alloc>::PopLast() {
694  // assert if the cache is empty
695  assert(last_ != map_.end());
696  // assert that our item's 'next' is pointing indeed to the end of the cache
697  assert(last_->second.next_ == map_.end());
698 
699  Erase(last_, true);
700 }
701 
702 template <typename Key, typename Value, typename CacheCostFunc,
703  typename Compare, template <typename> class Alloc>
704 inline const Key&
706  return m_it->first;
707 }
708 
709 template <typename Key, typename Value, typename CacheCostFunc,
710  typename Compare, template <typename> class Alloc>
711 inline const Value&
713  return m_it->second.value_;
714 }
715 
716 template <typename Key, typename Value, typename CacheCostFunc,
717  typename Compare, template <typename> class Alloc>
718 inline bool
720  const const_iterator& other) const {
721  return this->m_it == other.m_it;
722 }
723 
724 template <typename Key, typename Value, typename CacheCostFunc,
725  typename Compare, template <typename> class Alloc>
726 inline typename LruCache<Key, Value, CacheCostFunc, Compare,
727  Alloc>::const_iterator&
729 operator++() {
730  this->m_it = this->m_it->second.next_;
731  return *this;
732 }
733 
734 template <typename Key, typename Value, typename CacheCostFunc,
735  typename Compare, template <typename> class Alloc>
736 inline
739  operator++(int) {
740  typename MapType::const_iterator old_value = this->m_it;
741  this->m_it = this->m_it->second.next_;
742  return const_iterator{old_value};
743 }
744 
745 template <typename Key, typename Value, typename CacheCostFunc,
746  typename Compare, template <typename> class Alloc>
747 inline typename LruCache<Key, Value, CacheCostFunc, Compare,
748  Alloc>::const_iterator&
750 operator--() {
751  this->m_it = this->m_it->second.m_previous;
752  return *this;
753 }
754 
755 template <typename Key, typename Value, typename CacheCostFunc,
756  typename Compare, template <typename> class Alloc>
757 inline
760  operator--(int) {
761  typename MapType::const_iterator old_value = this->m_it;
762  this->m_it = this->m_it->second.m_previous;
763  return const_iterator{old_value};
764 }
765 
766 } // namespace utils
767 } // namespace olp
A type of objects to be stored.
Definition: LruCache.h:89
const Key & key() const
Gets the key of the ValueType object.
Definition: LruCache.h:705
MapType::const_iterator m_it
A typename of the map constant iterator.
Definition: LruCache.h:107
const Value & value() const
Gets the value of the ValueType object.
Definition: LruCache.h:712
A constant iterator of the LruCache object.
Definition: LruCache.h:111
bool operator!=(const const_iterator &other) const
Checks whether the values of the const_iterator parameter are not the same as the values of the other...
Definition: LruCache.h:152
std::ptrdiff_t difference_type
A typedef for the difference type.
Definition: LruCache.h:116
const value_type & reference
A typedef for the ValueType constant reference.
Definition: LruCache.h:120
const_iterator()=default
Creates a constant iterator object.
std::bidirectional_iterator_tag iterator_category
A typedef for the iterator category.
Definition: LruCache.h:114
const_iterator(const const_iterator &)=default
Creates a constant iterator object.
pointer operator->() const
Gets a pointer to this object.
Definition: LruCache.h:194
reference operator*() const
Gets a reference to this object.
Definition: LruCache.h:187
bool operator==(const const_iterator &other) const
Checks whether the values of the const_iterator parameter are the same as the values of the other par...
Definition: LruCache.h:719
const_iterator & operator++()
Iterates to the next LruCache object.
Definition: LruCache.h:729
ValueType value_type
A typedef for the ValueType type.
Definition: LruCache.h:118
const value_type * pointer
A typedef for the ValueType constant pointer.
Definition: LruCache.h:122
const_iterator & operator--()
Iterates to the previous LruCache object.
Definition: LruCache.h:750
const_iterator & operator=(const const_iterator &)=default
Copies this and the specified iterator to this.
A generic key-value LRU cache.
Definition: LruCache.h:69
std::size_t Size() const
Gets the current size of the cache.
Definition: LruCache.h:356
const_iterator Erase(const_iterator &it)
Removes a key from the cache.
Definition: LruCache.h:343
const_iterator FindNoPromote(const Key &key) const
Finds a value in the cache.
Definition: LruCache.h:405
void Resize(size_t maxSize)
Sets the new maximum size of the cache.
Definition: LruCache.h:373
typename MapType::key_compare CompareType
An alias for the key comparison function.
Definition: LruCache.h:79
LruCache(const AllocType &alloc=AllocType())
Creates an LruCache instance.
Definition: LruCache.h:217
void SetEvictionCallback(EvictionFunction func)
Sets a function that is invoked when a value is evicted from the cache.
Definition: LruCache.h:461
std::pair< const_iterator, bool > Insert(_Key &&key, _Value &&value)
Inserts a key-value pair in the cache.
bool Erase(const Key &key)
Removes a key from the cache.
Definition: LruCache.h:327
std::function< void(const Key &, Value &&)> EvictionFunction
An alias for the eviction function.
Definition: LruCache.h:76
typename MapType::allocator_type AllocType
An alias for the cache allocator type.
Definition: LruCache.h:82
LruCache(std::size_t maxSize, CacheCostFunc cacheCostFunc=CacheCostFunc(), const CompareType &compare=CompareType(), const AllocType &alloc=AllocType())
Creates an LruCache instance.
Definition: LruCache.h:233
const_iterator rend() const
Returns a reverse constant iterator to the end.
Definition: LruCache.h:435
const_iterator rbegin() const
Returns a reverse constant iterator to the beginning.
Definition: LruCache.h:432
const_iterator Find(const Key &key)
Finds a value in the cache.
Definition: LruCache.h:388
LruCache & operator=(LruCache &&other) noexcept
The default move assignment operator.
Definition: LruCache.h:262
const Value & Find(const Key &key, const Value &nullValue)
Finds a value in the cache.
Definition: LruCache.h:420
void Clear()
Removes all items from the cache.
Definition: LruCache.h:443
LruCache(const LruCache &)=delete
The deleted copy constructor.
std::size_t GetMaxSize() const
Gets the maximum size of the cache.
Definition: LruCache.h:363
std::pair< const_iterator, bool > InsertOrAssign(Key key, _Value &&value)
Inserts a key-value pair in the cache or updates an existing key-value pair.
LruCache & operator=(const LruCache &)=delete
The deleted assignment operator.
LruCache(LruCache &&other) noexcept
The default move constructor.
Definition: LruCache.h:247
const_iterator begin() const
Returns a constant iterator to the beginning.
Definition: LruCache.h:426
const_iterator end() const
Returns a constant iterator to the end.
Definition: LruCache.h:429
Rules all the other namespaces.
Definition: AppleSignInProperties.h:24
The default cost operator for LruCache.
Definition: LruCache.h:41
std::size_t operator()(const T &) const
Gets the size of the specified object.
Definition: LruCache.h:43