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()< cache([](int key) { return QString::number(key);}, 100,0.8);
+
+ QString sum;
+ for (int i=0; i<100; i++) {
+ sum+=cache.get(i);
+ }
+ qDebug()<<"sum.size()="< cache([](int key) { return QString::number(key);}, 100,0.8);
+
+ QString sum;
+ for (int i=0; i<100; i++) {
+ sum+=cache.get(i);
+ }
+ qDebug()<<"sum.size()="< cache([](int key) { return QString::number(key);}, 100,0.8);
+
+ int sum=cache.get(1).size();
+ QBENCHMARK(sum+=cache.get(1).size());
+ int i=1;
+ QBENCHMARK(sum+=cache.get(++i).size());
+ qDebug()<<"sum.size()="<