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-04-28 23:18:46 Functions: 179 201 89.1 %

          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     5090586 :     void lock()
      52             :     {
      53     5090586 :     }
      54             : 
      55     5090586 :     void unlock()
      56             :     {
      57     5090586 :     }
      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        1783 :     KeyValuePair(const K &k, const V &v) : key(k), value(v)
      83             :     {
      84        1783 :     }
      85             : 
      86       13980 :     KeyValuePair(const K &k, V &&v) : key(k), value(std::move(v))
      87             :     {
      88       13980 :     }
      89             : };
      90             : 
      91             : /**
      92             :  *  The LRU Cache class templated by
      93             :  *    Key - key type
      94             :  *    Value - value type
      95             :  *    MapType - an associative container like std::unordered_map
      96             :  *    LockType - a lock type derived from the Lock class (default:
      97             :  *NullLock = no synchronization)
      98             :  *
      99             :  *  The default NullLock based template is not thread-safe, however passing
     100             :  *Lock=std::mutex will make it
     101             :  *  thread-safe
     102             :  */
     103             : template <class Key, class Value, class Lock = NullLock,
     104             :           class Map = std::unordered_map<
     105             :               Key, typename std::list<KeyValuePair<Key, Value>>::iterator>>
     106             : class Cache
     107             : {
     108             :   public:
     109             :     typedef KeyValuePair<Key, Value> node_type;
     110             :     typedef std::list<KeyValuePair<Key, Value>> list_type;
     111             :     typedef Map map_type;
     112             :     typedef Lock lock_type;
     113             :     using Guard = std::lock_guard<lock_type>;
     114             : 
     115             :     /**
     116             :      * the max size is the hard limit of keys and (maxSize + elasticity) is the
     117             :      * soft limit
     118             :      * the cache is allowed to grow till maxSize + elasticity and is pruned back
     119             :      * to maxSize keys
     120             :      * set maxSize = 0 for an unbounded cache (but in that case, you're better
     121             :      * off using a std::unordered_map directly anyway! :)
     122             :      */
     123       63142 :     explicit Cache(size_t maxSize = 64, size_t elasticity = 10)
     124       63142 :         : maxSize_(maxSize), elasticity_(elasticity)
     125             :     {
     126       63122 :     }
     127             : 
     128       61759 :     virtual ~Cache() = default;
     129             : 
     130         769 :     size_t size() const
     131             :     {
     132        1538 :         Guard g(lock_);
     133         769 :         return cache_.size();
     134             :     }
     135             : 
     136        3366 :     bool empty() const
     137             :     {
     138        6732 :         Guard g(lock_);
     139        3366 :         return cache_.empty();
     140             :     }
     141             : 
     142       47390 :     void clear()
     143             :     {
     144       94780 :         Guard g(lock_);
     145       47390 :         cache_.clear();
     146       47390 :         keys_.clear();
     147       47390 :     }
     148             : 
     149        2501 :     void insert(const Key &k, const Value &v)
     150             :     {
     151        2501 :         Guard g(lock_);
     152        2501 :         const auto iter = cache_.find(k);
     153        2501 :         if (iter != cache_.end())
     154             :         {
     155         718 :             iter->second->value = v;
     156         718 :             keys_.splice(keys_.begin(), keys_, iter->second);
     157         718 :             return;
     158             :         }
     159             : 
     160        1783 :         keys_.emplace_front(k, v);
     161        1783 :         cache_[k] = keys_.begin();
     162        1783 :         prune();
     163             :     }
     164             : 
     165       14591 :     Value &insert(const Key &k, Value &&v)
     166             :     {
     167       29182 :         Guard g(lock_);
     168       14591 :         const auto iter = cache_.find(k);
     169       14591 :         if (iter != cache_.end())
     170             :         {
     171         611 :             iter->second->value = std::move(v);
     172         611 :             keys_.splice(keys_.begin(), keys_, iter->second);
     173         611 :             return keys_.front().value;
     174             :         }
     175             : 
     176       13980 :         keys_.emplace_front(k, std::move(v));
     177       13980 :         cache_[k] = keys_.begin();
     178       13980 :         prune();
     179       13980 :         return keys_.front().value;
     180             :     }
     181             : 
     182     4796856 :     bool tryGet(const Key &kIn, Value &vOut)
     183             :     {
     184     9593712 :         Guard g(lock_);
     185     4796856 :         const auto iter = cache_.find(kIn);
     186     4796856 :         if (iter == cache_.end())
     187             :         {
     188     4341589 :             return false;
     189             :         }
     190      455263 :         keys_.splice(keys_.begin(), keys_, iter->second);
     191      455263 :         vOut = iter->second->value;
     192      455263 :         return true;
     193             :     }
     194             : 
     195             :     /**
     196             :      *  The const reference returned here is only
     197             :      *    guaranteed to be valid till the next insert/delete
     198             :      */
     199           4 :     const Value &get(const Key &k)
     200             :     {
     201           5 :         Guard g(lock_);
     202           4 :         const auto iter = cache_.find(k);
     203           4 :         if (iter == cache_.end())
     204             :         {
     205           1 :             throw KeyNotFound();
     206             :         }
     207           3 :         keys_.splice(keys_.begin(), keys_, iter->second);
     208           6 :         return iter->second->value;
     209             :     }
     210             : 
     211      190180 :     Value *getPtr(const Key &k)
     212             :     {
     213      380360 :         Guard g(lock_);
     214      190180 :         const auto iter = cache_.find(k);
     215      190180 :         if (iter == cache_.end())
     216             :         {
     217       11839 :             return nullptr;
     218             :         }
     219      178341 :         keys_.splice(keys_.begin(), keys_, iter->second);
     220      178341 :         return &(iter->second->value);
     221             :     }
     222             : 
     223             :     /**
     224             :      * returns a copy of the stored object (if found)
     225             :      */
     226           1 :     Value getCopy(const Key &k)
     227             :     {
     228           1 :         return get(k);
     229             :     }
     230             : 
     231        2405 :     bool remove(const Key &k)
     232             :     {
     233        4810 :         Guard g(lock_);
     234        2405 :         auto iter = cache_.find(k);
     235        2405 :         if (iter == cache_.end())
     236             :         {
     237         116 :             return false;
     238             :         }
     239        2289 :         keys_.erase(iter->second);
     240        2289 :         cache_.erase(iter);
     241        2289 :         return true;
     242             :     }
     243             : 
     244       13219 :     bool contains(const Key &k)
     245             :     {
     246       13219 :         Guard g(lock_);
     247       26438 :         return cache_.find(k) != cache_.end();
     248             :     }
     249             : 
     250           0 :     bool getOldestEntry(Key &kOut, Value &vOut)
     251             :     {
     252           0 :         Guard g(lock_);
     253           0 :         if (keys_.empty())
     254             :         {
     255           0 :             return false;
     256             :         }
     257           0 :         kOut = keys_.back().key;
     258           0 :         vOut = keys_.back().value;
     259           0 :         return true;
     260             :     }
     261             : 
     262         137 :     bool removeAndRecycleOldestEntry(Value &vOut)
     263             :     {
     264         274 :         Guard g(lock_);
     265         137 :         if (keys_.empty())
     266             :         {
     267           1 :             return false;
     268             :         }
     269         136 :         vOut = std::move(keys_.back().value);
     270         136 :         cache_.erase(keys_.back().key);
     271         136 :         keys_.pop_back();
     272         136 :         return true;
     273             :     }
     274             : 
     275         645 :     size_t getMaxSize() const
     276             :     {
     277         645 :         return maxSize_;
     278             :     }
     279             : 
     280           1 :     size_t getElasticity() const
     281             :     {
     282           1 :         return elasticity_;
     283             :     }
     284             : 
     285         120 :     size_t getMaxAllowedSize() const
     286             :     {
     287         120 :         return maxSize_ + elasticity_;
     288             :     }
     289             : 
     290       19173 :     template <typename F> void cwalk(F &f) const
     291             :     {
     292       38134 :         Guard g(lock_);
     293       19173 :         std::for_each(keys_.begin(), keys_.end(), f);
     294       19173 :     }
     295             : 
     296             :     Cache(Cache &&other)
     297             :         : cache_(std::move(other.cache_)), keys_(std::move(other.keys_)),
     298             :           maxSize_(other.maxSize_), elasticity_(other.elasticity_)
     299             :     {
     300             :     }
     301             : 
     302             :   protected:
     303       15763 :     size_t prune()
     304             :     {
     305       15763 :         size_t maxAllowed = maxSize_ + elasticity_;
     306       15763 :         if (maxSize_ == 0 || cache_.size() <= maxAllowed)
     307             :         { /* ERO: changed < to <= */
     308       15051 :             return 0;
     309             :         }
     310         712 :         size_t count = 0;
     311        7635 :         while (cache_.size() > maxSize_)
     312             :         {
     313        6923 :             cache_.erase(keys_.back().key);
     314        6923 :             keys_.pop_back();
     315        6923 :             ++count;
     316             :         }
     317         712 :         return count;
     318             :     }
     319             : 
     320             :   private:
     321             :     // Disallow copying.
     322             :     Cache(const Cache &) = delete;
     323             :     Cache &operator=(const Cache &) = delete;
     324             : 
     325             :     mutable Lock lock_{};
     326             :     Map cache_{};
     327             :     list_type keys_{};
     328             :     size_t maxSize_;
     329             :     size_t elasticity_;
     330             : };
     331             : 
     332             : }  // namespace lru11
     333             : 
     334             : /*! @endcond */

Generated by: LCOV version 1.14