diff --git a/examples/multithreaded/README.md b/examples/multithreaded/README.md index eeb5741847..16ed638f9b 100644 --- a/examples/multithreaded/README.md +++ b/examples/multithreaded/README.md @@ -97,10 +97,10 @@ This test results in the following numbers (on my AMD Ryzen5 8/16-core laptop): VERSION: 5.0.0 BUILD MODE: Release -SERIAL RESULTS:
runtime, overall = 2336.7ms
single runtimes = (97.3 +/- 158.4) ms
speedup = 1.00x
threads / available = 1 / 16


+SERIAL RESULTS:
runtime, overall = 4241.3ms
single runtimes = (176.7 +/- 319.6) ms
speedup = 1.00x
threads / available = 1 / 16


PARALLEL RESULTS:
-runtime, overall = 527.2ms
single runtimes = (166.4 +/- 8.1) ms
speedup = 7.58x
threads / available = 8 / 16
batch runs = 3

speedup vs. serial = 4.4x +runtime, overall = 1025.3ms
single runtimes = (325.0 +/- 15.0) ms
speedup = 7.61x
threads / available = 8 / 16
batch runs = 3

speedup vs. serial = 4.1x [comment]:RESULTS_END diff --git a/lib/jkqtcommon/jkqtpcachingtools.h b/lib/jkqtcommon/jkqtpcachingtools.h index dd4106bda9..f1c0f6a4a8 100644 --- a/lib/jkqtcommon/jkqtpcachingtools.h +++ b/lib/jkqtcommon/jkqtpcachingtools.h @@ -25,10 +25,24 @@ #define JKQTPCACHINGTOOLS_H #include "jkqtcommon/jkqtcommon_imexport.h" +#include "jkqtcommon/jkqtpmathtools.h" #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 @@ -45,51 +59,132 @@ * * 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 +template struct JKQTPDataCache { template - inline JKQTPDataCache(FF generateData): - m_generateData(std::forward(generateData)) + 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::type& key) { + template + inline TData get(const typename std::enable_if::value, TKeyInSignature>::type& key) { const TKey cacheKey=key; QReadLocker lockR(&m_mutex); - if (m_cache.contains(cacheKey)) return m_cache[cacheKey]; + 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); - if (m_cache.contains(cacheKey)) return m_cache[cacheKey]; - + 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[cacheKey]=newData; + m_cache.emplace(cacheKey,newData); return newData; } - template - inline TData get(const typename std::enable_if::type& key) { + template + inline TData get(const typename std::enable_if::value, TKeyInSignature>::type& key) { const TKey cacheKey=key; - if (m_cache.contains(cacheKey)) return m_cache[cacheKey]; + 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[cacheKey]=newData; + 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: - QHash m_cache; - mutable typename std::enable_if::type m_mutex; + 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()); + QList > allItems; + allItems.reserve(m_cacheLastUseTimestamps.size()); + for (auto it=m_cacheLastUseTimestamps.begin(); it!=m_cacheLastUseTimestamps.end(); ++it) { + allItems.emplaceBack(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; - Q_DISABLE_COPY(JKQTPDataCache) }; #endif // JKQTPCACHINGTOOLS_H diff --git a/lib/jkqtmathtext/jkqtmathtexttools.cpp b/lib/jkqtmathtext/jkqtmathtexttools.cpp index fa2bcd89c6..2f5c7fe7f4 100644 --- a/lib/jkqtmathtext/jkqtmathtexttools.cpp +++ b/lib/jkqtmathtext/jkqtmathtexttools.cpp @@ -32,6 +32,7 @@ #include #include #include +#include void initJKQTMathTextResources() @@ -884,6 +885,9 @@ QPainterPath JKQTMathTextMakeHBracePath(double x, double ybrace, double width, d namespace { struct JKQTMathTextCacheKeyBase { + JKQTMathTextCacheKeyBase(): + f(), ldpiX(0), ldpiY(0), pdpiX(0), pdpiY(0) + {} inline explicit JKQTMathTextCacheKeyBase(const QFont& f_, QPaintDevice *pd): f(f_) { @@ -904,7 +908,12 @@ namespace { inline bool operator==(const JKQTMathTextCacheKeyBase& other) const{ return ldpiX==other.ldpiX && ldpiY==other.ldpiY && pdpiX==other.pdpiX && pdpiY==other.pdpiY && f==other.f; - + }; + inline bool operator<(const JKQTMathTextCacheKeyBase& other) const{ + return ldpiX struct JKQTMathTextTBRDataH: public JKQTMathTextCacheKeyBase { + JKQTMathTextTBRDataH(): + JKQTMathTextCacheKeyBase(), text("") + {} inline explicit JKQTMathTextTBRDataH(const QFont& f_, const TText& text_, QPaintDevice *pd): JKQTMathTextCacheKeyBase(f_, pd), text(text_) { @@ -940,7 +952,12 @@ namespace { inline bool operator==(const JKQTMathTextTBRDataH& other) const{ return JKQTMathTextCacheKeyBase::operator==(other) && text==other.text; - + }; + inline bool operator!=(const JKQTMathTextTBRDataH& other) const{ + return JKQTMathTextCacheKeyBase::operator!=(other) && text!=other.text; + }; + inline bool operator<(const JKQTMathTextTBRDataH& other) const{ + return JKQTMathTextCacheKeyBase::operator<(other) && text +struct std::hash +{ + std::size_t operator()(const JKQTMathTextCacheKeyBase& data) const noexcept + { + return qHash(data.f)+std::hash()(data.ldpiX)+std::hash()(data.ldpiY)+std::hash()(data.pdpiX)+std::hash()(data.pdpiY); + } +}; + + + +template<> +struct std::hash> +{ + std::size_t operator()(const JKQTMathTextTBRDataH& data) const noexcept + { + return qHash(data.f)+qHash(data.text)+std::hash()(data.ldpiX)+std::hash()(data.ldpiY)+std::hash()(data.pdpiX)+std::hash()(data.pdpiY); + } +}; + +template<> +struct std::hash> +{ + std::size_t operator()(const JKQTMathTextTBRDataH& data) const noexcept + { + return qHash(data.f)+qHash(data.text)+std::hash()(data.ldpiX)+std::hash()(data.ldpiY)+std::hash()(data.pdpiX)+std::hash()(data.pdpiY); + } +}; QRectF JKQTMathTextGetTightBoundingRect(const QFont &f, const QString &text, QPaintDevice *pd) { //thread_local JKQTPDataCache cache( - static JKQTPDataCache,true,JKQTMathTextTBRDataHExt> cache( + static JKQTPDataCache,JKQTPDataCacheThreadSafe,JKQTMathTextTBRDataHExt> cache( [](const JKQTMathTextTBRDataHExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.tightBoundingRect(key.text); @@ -986,7 +1031,7 @@ QRectF JKQTMathTextGetTightBoundingRect(const QFont &f, const QString &text, QPa QRectF JKQTMathTextGetBoundingRect(const QFont &f, const QString &text, QPaintDevice *pd) { //thread_local JKQTPDataCache cache( - static JKQTPDataCache,true,JKQTMathTextTBRDataHExt> cache( + static JKQTPDataCache,JKQTPDataCacheThreadSafe,JKQTMathTextTBRDataHExt> cache( [](const JKQTMathTextTBRDataHExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.boundingRect(key.text); @@ -999,7 +1044,7 @@ QRectF JKQTMathTextGetBoundingRect(const QFont &f, const QString &text, QPaintDe qreal JKQTMathTextGetHorAdvance(const QFont &f, const QString &text, QPaintDevice *pd) { //thread_local JKQTPDataCache cache( - static JKQTPDataCache,true,JKQTMathTextTBRDataHExt> cache( + static JKQTPDataCache,JKQTPDataCacheThreadSafe,JKQTMathTextTBRDataHExt> cache( [](const JKQTMathTextTBRDataHExt& key) { const QFontMetricsF fm(key.f, key.pd); #if (QT_VERSION>=QT_VERSION_CHECK(5, 15, 0)) @@ -1016,7 +1061,7 @@ qreal JKQTMathTextGetHorAdvance(const QFont &f, const QString &text, QPaintDevic qreal JKQTMathTextGetRightBearing(const QFont &f, const QChar &text, QPaintDevice *pd) { //thread_local JKQTPDataCache cache( - static JKQTPDataCache,true,JKQTMathTextTBRDataHExt> cache( + static JKQTPDataCache,JKQTPDataCacheThreadSafe,JKQTMathTextTBRDataHExt> cache( [](const JKQTMathTextTBRDataHExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.rightBearing(key.text); @@ -1028,7 +1073,7 @@ qreal JKQTMathTextGetRightBearing(const QFont &f, const QChar &text, QPaintDevic qreal JKQTMathTextGetLeftBearing(const QFont &f, const QChar &text, QPaintDevice *pd) { //thread_local JKQTPDataCache cache( - static JKQTPDataCache,true,JKQTMathTextTBRDataHExt> cache( + static JKQTPDataCache,JKQTPDataCacheThreadSafe,JKQTMathTextTBRDataHExt> cache( [](const JKQTMathTextTBRDataHExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.leftBearing(key.text); @@ -1040,7 +1085,7 @@ qreal JKQTMathTextGetLeftBearing(const QFont &f, const QChar &text, QPaintDevice qreal JKQTMathTextGetFontAscent(const QFont &f, QPaintDevice *pd) { - static JKQTPDataCache cache( + static JKQTPDataCache cache( [](const JKQTMathTextCacheKeyBaseExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.ascent(); @@ -1051,7 +1096,7 @@ qreal JKQTMathTextGetFontAscent(const QFont &f, QPaintDevice *pd) qreal JKQTMathTextGetFontDescent(const QFont &f, QPaintDevice *pd) { - static JKQTPDataCache cache( + static JKQTPDataCache cache( [](const JKQTMathTextCacheKeyBaseExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.descent(); @@ -1062,7 +1107,7 @@ qreal JKQTMathTextGetFontDescent(const QFont &f, QPaintDevice *pd) qreal JKQTMathTextGetFontHeight(const QFont &f, QPaintDevice *pd) { - static JKQTPDataCache cache( + static JKQTPDataCache cache( [](const JKQTMathTextCacheKeyBaseExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.height(); @@ -1073,7 +1118,7 @@ qreal JKQTMathTextGetFontHeight(const QFont &f, QPaintDevice *pd) qreal JKQTMathTextGetFontLineWidth(const QFont &f, QPaintDevice *pd) { - static JKQTPDataCache cache( + static JKQTPDataCache cache( [](const JKQTMathTextCacheKeyBaseExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.lineWidth(); @@ -1084,7 +1129,7 @@ qreal JKQTMathTextGetFontLineWidth(const QFont &f, QPaintDevice *pd) qreal JKQTMathTextGetFontLeading(const QFont &f, QPaintDevice *pd) { - static JKQTPDataCache cache( + static JKQTPDataCache cache( [](const JKQTMathTextCacheKeyBaseExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.leading(); @@ -1095,7 +1140,7 @@ qreal JKQTMathTextGetFontLeading(const QFont &f, QPaintDevice *pd) qreal JKQTMathTextGetFontLineSpacing(const QFont &f, QPaintDevice *pd) { - static JKQTPDataCache cache( + static JKQTPDataCache cache( [](const JKQTMathTextCacheKeyBaseExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.lineSpacing(); @@ -1106,7 +1151,7 @@ qreal JKQTMathTextGetFontLineSpacing(const QFont &f, QPaintDevice *pd) qreal JKQTMathTextGetFontStrikoutPos(const QFont &f, QPaintDevice *pd) { - static JKQTPDataCache cache( + static JKQTPDataCache cache( [](const JKQTMathTextCacheKeyBaseExt& key) { const QFontMetricsF fm(key.f, key.pd); return fm.strikeOutPos(); diff --git a/screenshots/multithreaded.png b/screenshots/multithreaded.png index c4c7e0c673..9535687693 100644 Binary files a/screenshots/multithreaded.png and b/screenshots/multithreaded.png differ diff --git a/screenshots/multithreaded_small.png b/screenshots/multithreaded_small.png index b86c07773a..a8631366d5 100644 Binary files a/screenshots/multithreaded_small.png and b/screenshots/multithreaded_small.png differ diff --git a/tests/jkqtcommmon/jkqtcommon_test.cpp b/tests/jkqtcommmon/jkqtcommon_test.cpp index 4b397d94b3..b29458204c 100644 --- a/tests/jkqtcommmon/jkqtcommon_test.cpp +++ b/tests/jkqtcommmon/jkqtcommon_test.cpp @@ -1,6 +1,7 @@ #include #include #include "jkqtcommon/jkqtpcsstools.h" +#include "jkqtcommon/jkqtpcachingtools.h" #ifndef QCOMPARE_EQ #define QCOMPARE_EQ(A,B) if (!static_cast((A)==(B))) {qDebug()<