/* Copyright (c) 2008-2024 Jan W. Krieger () last modification: $LastChangedDate$ (revision $Rev$) This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License (LGPL) for more details. You should have received a copy of the GNU Lesser General Public License (LGPL) along with this program. If not, see . */ #ifndef JKQTPCACHINGTOOLS_H #define JKQTPCACHINGTOOLS_H #include "jkqtcommon/jkqtcommon_imexport.h" #include "jkqtcommon/jkqtpmathtools.h" #include #include #include #include #include #include #include #include #include #include /** \brief tag type to configure JKQTPDataCache for thread-safety * \ingroup jkqtptools_concurrency */ struct JKQTPDataCacheThreadSafe {}; /** \brief tag type to configure JKQTPDataCache for non thread-safety * \ingroup jkqtptools_concurrency */ struct JKQTPDataCacheNotThreadSafe {}; /** \brief this class can be used to implement a general cache for values * \ingroup jkqtptools_concurrency * * It is typically used to generate a static (ThreadSafe=true) of thread_local (ThreadSafe=false) cache inside a function. * * The class is parametrized by a key (TKeay) and value (TData) data type and receives (in the constructor) a functor that * calculates the data for a key. An additional template parameter ThreadSafe indicates (true|false) whether the class * is implemented in a thread-safe way (for static instances) or not (then it should be used as thread_local instances in a * multi-threaded environment or in a single-thread environment). * * The class member function get(key) returns a value for a given key, which is either taken from the internal cache, * or generated using the functor provided to the constructor. In the latter case the generated value is stored in the internal cache. * * Internally the cache maps TKey to TData, but the signature of the get()-function and the generator functor actually uses TKeyInSignature, * which may differ from TKey. The only limitation is that TKeyInSignature can be converted/assigned to a TKey * * The cache has a maximmum size m_maxEntries. * When you try to add a new object, after which the size would grow beyond this, a fraction 1-m_retainFraction of elements are * deleted from the cache. The delete strategy is least-recently used (LRU). In order to immplement this, the cache keeps track of * the last use timestamp of each entry. * * You can deactivate the cleaning by setting m_maxEntries<0, but the the cache may grow indefinitely and there is possibly undefined behaviour * when add one too many items! */ template struct JKQTPDataCache { template inline JKQTPDataCache(FF generateData, int maxEntries=10000, double retainFraction=0.8): m_maxEntries(maxEntries), m_retainFraction(retainFraction), m_generateData(std::forward(generateData)) { } JKQTPDataCache()=delete; JKQTPDataCache(const JKQTPDataCache&)=delete; JKQTPDataCache& operator=(const JKQTPDataCache&)=delete; JKQTPDataCache(JKQTPDataCache&&)=default; JKQTPDataCache& operator=(JKQTPDataCache&&)=default; template inline TData get_inline(Args... args) { return get(TKeyInSignature(args...)); } template inline TData get(const typename std::enable_if::value, TKeyInSignature>::type& key) { const TKey cacheKey=key; QReadLocker lockR(&m_mutex); auto it=m_cache.find(cacheKey); if (m_cache.end()!=it) { m_cacheLastUseTimestamps[cacheKey]->exchange(currenTimestamp()); return it->second; } lockR.unlock(); QWriteLocker lockW(&m_mutex); it=m_cache.find(cacheKey); if (m_cache.end()!=it) { m_cacheLastUseTimestamps.at(cacheKey)->exchange(currenTimestamp()); return it->second; } if (m_maxEntries>0 && m_cache.size()>=static_cast(m_maxEntries)) cleanCache_notThreadSafe(); m_cacheLastUseTimestamps.emplace(cacheKey, std::make_shared >(currenTimestamp())); const auto newData=m_generateData(key); m_cache.emplace(cacheKey,newData); return newData; } template inline TData get(const typename std::enable_if::value, TKeyInSignature>::type& key) { const TKey cacheKey=key; auto it=m_cache.find(cacheKey); if (m_cache.end()!=it) { m_cacheLastUseTimestamps[cacheKey]->exchange(currenTimestamp()); return it->second; } if (m_maxEntries>0 && m_cache.size()>=static_cast(m_maxEntries)) cleanCache_notThreadSafe(); const auto newData=m_generateData(key); m_cache.emplace(cacheKey,newData); m_cacheLastUseTimestamps.emplace(cacheKey, std::make_shared >(currenTimestamp())); return newData; } template inline bool contains(const typename std::enable_if::value, TKeyInSignature>::type& key) const { const TKey cacheKey=key; QReadLocker lockR(&m_mutex); return m_cache.find(cacheKey)!=m_cache.end(); } template inline bool contains(const typename std::enable_if::value, TKeyInSignature>::type& key) const { const TKey cacheKey=key; return m_cache.find(cacheKey)!=m_cache.end(); } inline int size() const { return size_impl(); } private: template inline typename std::enable_if::value, int>::type size_impl() const { QReadLocker lockR(&m_mutex); return m_cache.size(); } template inline typename std::enable_if::value, int>::type size_impl() const { return m_cache.size(); } /** \brief generate a timestamp */ static inline int64_t currenTimestamp() { static auto firstTime=std::chrono::steady_clock::now(); return std::chrono::duration_cast(std::chrono::steady_clock::now()-firstTime).count(); } /** \brief clean the cache, so at m_retainFraction*m_maxEntries entries remain. */ inline void cleanCache_notThreadSafe() { if (m_maxEntries<0 || m_cache.size()(m_maxEntries)) return; const int deleteItems=jkqtp_boundedRoundTo(1, (1.0-m_retainFraction)*static_cast(m_cache.size()), m_cache.size()); std::vector > allItems; allItems.reserve(m_cacheLastUseTimestamps.size()); for (auto it=m_cacheLastUseTimestamps.begin(); it!=m_cacheLastUseTimestamps.end(); ++it) { allItems.emplace_back(it->first, it->second->load()); } std::sort(allItems.begin(), allItems.end(), [](const QPair&a, const QPair&b) {return a.second>b.second;}); for (int i=0; i m_cache; std::unordered_map > > m_cacheLastUseTimestamps; mutable QReadWriteLock m_mutex; const std::function m_generateData; }; #endif // JKQTPCACHINGTOOLS_H