LCOV - code coverage report
Current view: top level - utilities/include/mcrl2/utilities - fixed_size_cache.h (source / functions) Hit Total Coverage
Test: mcrl2_coverage.info.cleaned Lines: 17 21 81.0 %
Date: 2024-04-26 03:18:02 Functions: 4 4 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Author(s): Maurice Laveaux
       2             : // Copyright: see the accompanying file COPYING or copy at
       3             : // https://github.com/mCRL2org/mCRL2/blob/master/COPYING
       4             : //
       5             : // Distributed under the Boost Software License, Version 1.0.
       6             : // (See accompanying file LICENSE_1_0.txt or copy at
       7             : // http://www.boost.org/LICENSE_1_0.txt)
       8             : //
       9             : 
      10             : #ifndef MCRL2_UTILITIES_FIXED_SIZE_CACHE_H
      11             : #define MCRL2_UTILITIES_FIXED_SIZE_CACHE_H
      12             : 
      13             : #include "mcrl2/utilities/cache_policy.h"
      14             : #include "mcrl2/utilities/unordered_map.h"
      15             : 
      16             : namespace mcrl2
      17             : {
      18             : namespace utilities
      19             : {
      20             : 
      21             : /// \brief A cache keeps track of key-value pairs similar to a map. The difference is that a cache
      22             : ///        has (an optional) maximum size and a policy that determines what element gets evicted when
      23             : ///        the cache is full.
      24             : /// \details Works with arbirary maps that implement the unordered_map interface.
      25             : template<typename Policy>
      26             : class fixed_size_cache
      27             : {
      28             : public:
      29             :   using key_type = typename Policy::key_type;
      30             :   using iterator = typename Policy::map_type::iterator;
      31             :   using const_iterator = typename Policy::map_type::const_iterator;
      32             : 
      33           1 :   explicit fixed_size_cache(std::size_t max_size = 1024)
      34           1 :     : m_map(max_size)
      35             :   {
      36           1 :     if (max_size == 0)
      37             :     {
      38           0 :       m_maximum_size = std::numeric_limits<std::size_t>::max();
      39             :     }
      40             :     else
      41             :     {
      42             :       // The reason for this is that the internal mapping might only support powers of two and as such
      43             :       // we might as well use the additional capacity.
      44           1 :       m_maximum_size = m_map.capacity();
      45             :     }
      46           1 :   }
      47             : 
      48             :   const_iterator begin() const { return m_map.begin(); }
      49             :   const_iterator end() const { return m_map.end(); }
      50             : 
      51             :   void clear() { m_map.clear(); m_policy.clear(); }
      52             : 
      53             :   std::size_t count(const key_type& key) const { return m_map.count(key); }
      54             : 
      55         100 :   iterator find(const key_type& key)
      56             :   {
      57         100 :     return m_map.find(key);
      58             :   }
      59             : 
      60             :   /// \brief Stores the given key-value pair in the cache. Depending on the cache policy and capacity an existing element
      61             :   ///        might be removed.
      62             :   template<typename ...Args>
      63             :   std::pair<iterator, bool> emplace(Args&&... args)
      64             :   {
      65             :     // The reason to split the find and emplace is that when we insert an element the replacement_candidate should not be
      66             :     // the key that we just inserted. The other way around, when an element that we are looking for was first removed and
      67             :     // then searched for also leads to unnecessary inserts.
      68             :     auto result = find(args...);
      69             :     if (result == m_map.end())
      70             :     {
      71             :       // If the cache would be full after an inserted.
      72             :       if (m_map.size() + 1 >= m_maximum_size)
      73             :       {
      74             :         // Remove an existing element defined by the policy.
      75             :         m_map.erase(m_policy.replacement_candidate(m_map));
      76             :       }
      77             : 
      78             :       // Insert an element and inform the policy that an element was inserted.
      79             :       auto emplace_result = m_map.emplace(std::forward<Args>(args)...);
      80             :       m_policy.inserted((*emplace_result.first).first);
      81             :       return emplace_result;
      82             :     }
      83             : 
      84             :     assert(m_map.size() <= m_maximum_size);
      85             :     return std::make_pair(result, false);
      86             :   }
      87             : 
      88             : protected:
      89             :   typename Policy::map_type m_map;    ///< The underlying mapping from keys to their cached results.
      90             :   Policy                    m_policy; ///< The replacement policy for keys in the cache.
      91             : 
      92             :   std::size_t m_maximum_size; ///< The maximum number of elements to cache.
      93             : };
      94             : 
      95             : /// \brief A cache keeps track of key-value pairs similar to a map. The difference is that a cache
      96             : ///        has (an optional) maximum size and a policy that determines what element gets evicted when
      97             : ///        the cache is full.
      98             : /// \details Works with arbirary maps that implement the unordered_map interface.
      99             : template<typename Policy, typename F, typename ...Args>
     100             : class function_cache : public fixed_size_cache<Policy>
     101             : {
     102             : private:
     103             :   using super = fixed_size_cache<Policy>;
     104             :   using super::m_map;
     105             :   using super::m_maximum_size;
     106             :   using super::m_policy;
     107             :   using super::find;
     108             : 
     109             : public:
     110           1 :   explicit function_cache(F cached_function = F(), std::size_t max_size = 1024)
     111             :     : super(max_size),
     112           1 :       m_cached_function(cached_function)
     113           1 :   {}
     114             : 
     115             :   /// \brief Stores the given key-value pair in the cache. Depending on the cache policy and capacity an existing element
     116             :   ///        might be removed.
     117         100 :   auto operator()(Args... args) -> typename std::invoke_result_t<F, Args...>
     118             :   {
     119             :     // The reason to split the find and emplace is that when we insert an element the replacement_candidate should not be
     120             :     // the key that we just inserted. The other way around, when an element that we are looking for was first removed and
     121             :     // then searched for also leads to unnecessary inserts.
     122         100 :     auto result = find(args...);
     123         100 :     if (result == m_map.end())
     124             :     {
     125             :       // If the cache would be full after an inserted.
     126         100 :       if (m_map.size() + 1 >= m_maximum_size)
     127             :       {
     128             :         // Remove an existing element defined by the policy.
     129           0 :         m_map.erase(m_policy.replacement_candidate(m_map));
     130             :       }
     131             : 
     132             :       // Insert an element and inform the policy that an element was inserted.
     133         100 :       auto emplace_result = m_map.emplace(std::make_pair(args..., m_cached_function(args...)));
     134             : 
     135         100 :       m_policy.inserted((*emplace_result.first).first);
     136         100 :       return emplace_result.first->second;
     137             :     }
     138             : 
     139           0 :     assert(m_map.size() <= m_maximum_size);
     140           0 :     return result->second;
     141             :   }
     142             : 
     143             : private:
     144             :   F m_cached_function; ///< The function of which the results are cached.
     145             : };
     146             : 
     147             : 
     148             : template<typename Key, typename T>
     149             : using fifo_cache = fixed_size_cache<fifo_policy<mcrl2::utilities::unordered_map<Key, T>>>;
     150             : 
     151             : template<typename F, typename Args>
     152             : using fifo_function_cache = function_cache<
     153             :   fifo_policy<mcrl2::utilities::unordered_map<Args, decltype(std::declval<F>()(std::declval<Args>()))>>,
     154             :   F,
     155             :   Args>;
     156             : 
     157             : } // namespace utilities
     158             : } // namespace mcrl2
     159             : 
     160             : #endif // MCRL2_UTILITIES_FIXED_SIZE_CACHE_H

Generated by: LCOV version 1.14