LCOV - code coverage report
Current view: top level - port - cpl_mem_cache.h (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 109 116 94.0 %
Date: 2024-11-21 22:18:42 Functions: 195 219 89.0 %

          Line data    Source code
       1             : /*
       2             :  * LRUCache11 - a templated C++11 based LRU cache class that allows
       3             :  * specification of
       4             :  * key, value and optionally the map container type (defaults to
       5             :  * std::unordered_map)
       6             :  * By using the std::map and a linked list of keys it allows O(1) insert, delete
       7             :  * and
       8             :  * refresh operations.
       9             :  *
      10             :  * This is a header-only library and all you need is the LRUCache11.hpp file
      11             :  *
      12             :  * Github: https://github.com/mohaps/lrucache11
      13             :  *
      14             :  * This is a follow-up to the LRUCache project -
      15             :  * https://github.com/mohaps/lrucache
      16             :  *
      17             :  * Copyright (c) 2012-22 SAURAV MOHAPATRA <mohaps@gmail.com>
      18             :  *
      19             :  * Permission to use, copy, modify, and distribute this software for any
      20             :  * purpose with or without fee is hereby granted, provided that the above
      21             :  * copyright notice and this permission notice appear in all copies.
      22             :  *
      23             :  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      24             :  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
      25             :  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
      26             :  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
      27             :  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
      28             :  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
      29             :  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
      30             :  */
      31             : 
      32             : /*! @cond Doxygen_Suppress */
      33             : 
      34             : #pragma once
      35             : #include <algorithm>
      36             : #include <cstdint>
      37             : #include <list>
      38             : #include <mutex>
      39             : #include <stdexcept>
      40             : #include <thread>
      41             : #include <unordered_map>
      42             : 
      43             : namespace lru11
      44             : {
      45             : /*
      46             :  * a noop lockable concept that can be used in place of std::mutex
      47             :  */
      48             : class NullLock
      49             : {
      50             :   public:
      51     5830456 :     void lock()
      52             :     {
      53     5830456 :     }
      54             : 
      55     5826666 :     void unlock()
      56             :     {
      57     5826666 :     }
      58             : 
      59             :     bool try_lock()
      60             :     {
      61             :         return true;
      62             :     }
      63             : };
      64             : 
      65             : /**
      66             :  * error raised when a key not in cache is passed to get()
      67             :  */
      68             : class KeyNotFound : public std::invalid_argument
      69             : {
      70             :   public:
      71           1 :     KeyNotFound() : std::invalid_argument("key_not_found")
      72             :     {
      73           1 :     }
      74             : };
      75             : 
      76             : template <typename K, typename V> struct KeyValuePair
      77             : {
      78             :   public:
      79             :     K key;
      80             :     V value;
      81             : 
      82        4290 :     KeyValuePair(const K &k, const V &v) : key(k), value(v)
      83             :     {
      84        4290 :     }
      85             : 
      86       14472 :     KeyValuePair(const K &k, V &&v) : key(k), value(std::move(v))
      87             :     {
      88       14472 :     }
      89             : 
      90             :   private:
      91             :     KeyValuePair(const KeyValuePair &) = delete;
      92             :     KeyValuePair &operator=(const KeyValuePair &) = delete;
      93             : };
      94             : 
      95             : /**
      96             :  *  The LRU Cache class templated by
      97             :  *    Key - key type
      98             :  *    Value - value type
      99             :  *    MapType - an associative container like std::unordered_map
     100             :  *    LockType - a lock type derived from the Lock class (default:
     101             :  *NullLock = no synchronization)
     102             :  *
     103             :  *  The default NullLock based template is not thread-safe, however passing
     104             :  *Lock=std::mutex will make it
     105             :  *  thread-safe
     106             :  */
     107             : template <class Key, class Value, class Lock = NullLock,
     108             :           class Map = std::unordered_map<
     109             :               Key, typename std::list<KeyValuePair<Key, Value>>::iterator>>
     110             : class Cache
     111             : {
     112             :   public:
     113             :     typedef KeyValuePair<Key, Value> node_type;
     114             :     typedef std::list<KeyValuePair<Key, Value>> list_type;
     115             :     typedef Map map_type;
     116             :     typedef Lock lock_type;
     117             :     using Guard = std::lock_guard<lock_type>;
     118             : 
     119             :     /**
     120             :      * the max size is the hard limit of keys and (maxSize + elasticity) is the
     121             :      * soft limit
     122             :      * the cache is allowed to grow till maxSize + elasticity and is pruned back
     123             :      * to maxSize keys
     124             :      * set maxSize = 0 for an unbounded cache (but in that case, you're better
     125             :      * off using a std::unordered_map directly anyway! :)
     126             :      */
     127       70774 :     explicit Cache(size_t maxSize = 64, size_t elasticity = 10)
     128       70774 :         : maxSize_(maxSize), elasticity_(elasticity)
     129             :     {
     130       70714 :     }
     131             : 
     132       70248 :     virtual ~Cache() = default;
     133             : 
     134         803 :     size_t size() const
     135             :     {
     136        1606 :         Guard g(lock_);
     137         803 :         return cache_.size();
     138             :     }
     139             : 
     140        3511 :     bool empty() const
     141             :     {
     142        7022 :         Guard g(lock_);
     143        3511 :         return cache_.empty();
     144             :     }
     145             : 
     146       53204 :     void clear()
     147             :     {
     148      106408 :         Guard g(lock_);
     149       53204 :         cache_.clear();
     150       53204 :         keys_.clear();
     151       53204 :     }
     152             : 
     153        5081 :     void insert(const Key &k, const Value &v)
     154             :     {
     155        5081 :         Guard g(lock_);
     156        5081 :         const auto iter = cache_.find(k);
     157        5081 :         if (iter != cache_.end())
     158             :         {
     159         791 :             iter->second->value = v;
     160         791 :             keys_.splice(keys_.begin(), keys_, iter->second);
     161         791 :             return;
     162             :         }
     163             : 
     164        4290 :         keys_.emplace_front(k, v);
     165        4290 :         cache_[k] = keys_.begin();
     166        4290 :         prune();
     167             :     }
     168             : 
     169       15138 :     Value &insert(const Key &k, Value &&v)
     170             :     {
     171       30277 :         Guard g(lock_);
     172       15139 :         const auto iter = cache_.find(k);
     173       15138 :         if (iter != cache_.end())
     174             :         {
     175         667 :             iter->second->value = std::move(v);
     176         667 :             keys_.splice(keys_.begin(), keys_, iter->second);
     177         667 :             return keys_.front().value;
     178             :         }
     179             : 
     180       14471 :         keys_.emplace_front(k, std::move(v));
     181       14471 :         cache_[k] = keys_.begin();
     182       14471 :         prune();
     183       14472 :         return keys_.front().value;
     184             :     }
     185             : 
     186     5525736 :     bool tryGet(const Key &kIn, Value &vOut)
     187             :     {
     188    11048070 :         Guard g(lock_);
     189     5523595 :         const auto iter = cache_.find(kIn);
     190     5523022 :         if (iter == cache_.end())
     191             :         {
     192     4385929 :             return false;
     193             :         }
     194     1134903 :         keys_.splice(keys_.begin(), keys_, iter->second);
     195     1137864 :         vOut = iter->second->value;
     196     1136404 :         return true;
     197             :     }
     198             : 
     199             :     /**
     200             :      *  The const reference returned here is only
     201             :      *    guaranteed to be valid till the next insert/delete
     202             :      */
     203           4 :     const Value &get(const Key &k)
     204             :     {
     205           5 :         Guard g(lock_);
     206           4 :         const auto iter = cache_.find(k);
     207           4 :         if (iter == cache_.end())
     208             :         {
     209           1 :             throw KeyNotFound();
     210             :         }
     211           3 :         keys_.splice(keys_.begin(), keys_, iter->second);
     212           6 :         return iter->second->value;
     213             :     }
     214             : 
     215      187609 :     Value *getPtr(const Key &k)
     216             :     {
     217      375218 :         Guard g(lock_);
     218      187609 :         const auto iter = cache_.find(k);
     219      187608 :         if (iter == cache_.end())
     220             :         {
     221       12099 :             return nullptr;
     222             :         }
     223      175509 :         keys_.splice(keys_.begin(), keys_, iter->second);
     224      175510 :         return &(iter->second->value);
     225             :     }
     226             : 
     227             :     /**
     228             :      * returns a copy of the stored object (if found)
     229             :      */
     230           1 :     Value getCopy(const Key &k)
     231             :     {
     232           1 :         return get(k);
     233             :     }
     234             : 
     235        2619 :     bool remove(const Key &k)
     236             :     {
     237        5238 :         Guard g(lock_);
     238        2619 :         auto iter = cache_.find(k);
     239        2619 :         if (iter == cache_.end())
     240             :         {
     241         113 :             return false;
     242             :         }
     243        2506 :         keys_.erase(iter->second);
     244        2506 :         cache_.erase(iter);
     245        2506 :         return true;
     246             :     }
     247             : 
     248       15798 :     bool contains(const Key &k)
     249             :     {
     250       15798 :         Guard g(lock_);
     251       31596 :         return cache_.find(k) != cache_.end();
     252             :     }
     253             : 
     254           0 :     bool getOldestEntry(Key &kOut, Value &vOut)
     255             :     {
     256           0 :         Guard g(lock_);
     257           0 :         if (keys_.empty())
     258             :         {
     259           0 :             return false;
     260             :         }
     261           0 :         kOut = keys_.back().key;
     262           0 :         vOut = keys_.back().value;
     263           0 :         return true;
     264             :     }
     265             : 
     266         137 :     bool removeAndRecycleOldestEntry(Value &vOut)
     267             :     {
     268         274 :         Guard g(lock_);
     269         137 :         if (keys_.empty())
     270             :         {
     271           1 :             return false;
     272             :         }
     273         136 :         vOut = std::move(keys_.back().value);
     274         136 :         cache_.erase(keys_.back().key);
     275         136 :         keys_.pop_back();
     276         136 :         return true;
     277             :     }
     278             : 
     279         651 :     size_t getMaxSize() const
     280             :     {
     281         651 :         return maxSize_;
     282             :     }
     283             : 
     284           1 :     size_t getElasticity() const
     285             :     {
     286           1 :         return elasticity_;
     287             :     }
     288             : 
     289         148 :     size_t getMaxAllowedSize() const
     290             :     {
     291         148 :         return maxSize_ + elasticity_;
     292             :     }
     293             : 
     294       21445 :     template <typename F> void cwalk(F &f) const
     295             :     {
     296       42676 :         Guard g(lock_);
     297       21445 :         std::for_each(keys_.begin(), keys_.end(), f);
     298       21445 :     }
     299             : 
     300             :     Cache(Cache &&other)
     301             :         : cache_(std::move(other.cache_)), keys_(std::move(other.keys_)),
     302             :           maxSize_(other.maxSize_), elasticity_(other.elasticity_)
     303             :     {
     304             :     }
     305             : 
     306             :   protected:
     307       18762 :     size_t prune()
     308             :     {
     309       18762 :         size_t maxAllowed = maxSize_ + elasticity_;
     310       18762 :         if (maxSize_ == 0 || cache_.size() <= maxAllowed)
     311             :         { /* ERO: changed < to <= */
     312       17746 :             return 0;
     313             :         }
     314        1016 :         size_t count = 0;
     315        9962 :         while (cache_.size() > maxSize_)
     316             :         {
     317        8946 :             cache_.erase(keys_.back().key);
     318        8946 :             keys_.pop_back();
     319        8946 :             ++count;
     320             :         }
     321        1015 :         return count;
     322             :     }
     323             : 
     324             :   private:
     325             :     // Disallow copying.
     326             :     Cache(const Cache &) = delete;
     327             :     Cache &operator=(const Cache &) = delete;
     328             : 
     329             :     mutable Lock lock_{};
     330             :     Map cache_{};
     331             :     list_type keys_{};
     332             :     size_t maxSize_;
     333             :     size_t elasticity_;
     334             : };
     335             : 
     336             : }  // namespace lru11
     337             : 
     338             : /*! @endcond */

Generated by: LCOV version 1.14