[LyX/master] Improve (modestly) the performance of font metrics caches
Jean-Marc Lasgouttes
lasgouttes at lyx.org
Fri Sep 24 14:51:35 UTC 2021
commit 6bbd88accfb76248d00346f308ba1f67f186e094
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date: Fri Sep 24 16:57:05 2021 +0200
Improve (modestly) the performance of font metrics caches
This fixes two performance issues and improves the performance of
TextMetrics::redoParagraph by 15% in a workload that uses the cache a
lot. The difference will be much less when the cache is not used much.
1/ repetion of the hash code computation
The code
if (cache.contains(key))
result = cache[key]:
is not efficient, since qHash(key) has to be computed twice.
To fix this a new Cache::object_str() method is added, which allows
if (auto * obj = cache.object(key))
result = *obj;
2/ code of has code computation
Instead of using a verbose string that is complicated to build as
key, new key structs BreakAtKey and TextLayoutKey are introduced,
along with the relevant qHash() implementation.
---
src/frontends/qt/GuiFontMetrics.cpp | 34 ++++++++++++++++++++++------------
src/frontends/qt/GuiFontMetrics.h | 30 ++++++++++++++++++++++++++++--
src/support/Cache.h | 2 ++
3 files changed, 52 insertions(+), 14 deletions(-)
diff --git a/src/frontends/qt/GuiFontMetrics.cpp b/src/frontends/qt/GuiFontMetrics.cpp
index 7d72820..8273ed8 100644
--- a/src/frontends/qt/GuiFontMetrics.cpp
+++ b/src/frontends/qt/GuiFontMetrics.cpp
@@ -253,8 +253,8 @@ int GuiFontMetrics::rbearing(char_type c) const
int GuiFontMetrics::width(docstring const & s) const
{
PROFILE_THIS_BLOCK(width);
- if (strwidth_cache_.contains(s))
- return strwidth_cache_[s];
+ if (int * wid_p = strwidth_cache_.object_ptr(s))
+ return *wid_p;
PROFILE_CACHE_MISS(width);
/* Several problems have to be taken into account:
* * QFontMetrics::width does not returns a wrong value with Qt5 with
@@ -328,14 +328,19 @@ int GuiFontMetrics::signedWidth(docstring const & s) const
}
+uint qHash(TextLayoutKey const & key)
+{
+ double params = (2 * key.rtl - 1) * key.ws;
+ return std::qHash(key.s) ^ ::qHash(params);
+}
+
shared_ptr<QTextLayout const>
GuiFontMetrics::getTextLayout(docstring const & s, bool const rtl,
double const wordspacing) const
{
PROFILE_THIS_BLOCK(getTextLayout);
- docstring const s_cache =
- s + (rtl ? "r" : "l") + convert<docstring>(wordspacing);
- if (auto ptl = qtextlayout_cache_[s_cache])
+ TextLayoutKey key{s, rtl, wordspacing};
+ if (auto ptl = qtextlayout_cache_[key])
return ptl;
PROFILE_CACHE_MISS(getTextLayout);
auto const ptl = make_shared<QTextLayout>();
@@ -368,7 +373,7 @@ GuiFontMetrics::getTextLayout(docstring const & s, bool const rtl,
ptl->beginLayout();
ptl->createLine();
ptl->endLayout();
- qtextlayout_cache_.insert(s_cache, ptl);
+ qtextlayout_cache_.insert(key, ptl);
return ptl;
}
@@ -547,22 +552,27 @@ GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
}
+uint qHash(BreakAtKey const & key)
+{
+ int params = key.force + 2 * key.rtl + 4 * key.x;
+ return std::qHash(key.s) ^ ::qHash(params);
+}
+
+
bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const force) const
{
PROFILE_THIS_BLOCK(breakAt);
if (s.empty())
return false;
- docstring const s_cache =
- s + convert<docstring>(x) + (rtl ? "r" : "l") + (force ? "f" : "w");
+ BreakAtKey key{s, x, rtl, force};
pair<int, int> pp;
-
- if (breakat_cache_.contains(s_cache))
- pp = breakat_cache_[s_cache];
+ if (auto * pp_ptr = breakat_cache_.object_ptr(key))
+ pp = *pp_ptr;
else {
PROFILE_CACHE_MISS(breakAt);
pp = breakAt_helper(s, x, rtl, force);
- breakat_cache_.insert(s_cache, pp, s_cache.size() * sizeof(char_type));
+ breakat_cache_.insert(key, pp, sizeof(key) + s.size() * sizeof(char_type));
}
if (pp.first == -1)
return false;
diff --git a/src/frontends/qt/GuiFontMetrics.h b/src/frontends/qt/GuiFontMetrics.h
index e9af0dd..95b577c 100644
--- a/src/frontends/qt/GuiFontMetrics.h
+++ b/src/frontends/qt/GuiFontMetrics.h
@@ -27,6 +27,32 @@
namespace lyx {
namespace frontend {
+struct BreakAtKey
+{
+ bool operator==(BreakAtKey const & key) const {
+ return key.s == s && key.x == x && key.rtl == rtl && key.force == force;
+ }
+
+ docstring s;
+ int x;
+ bool rtl;
+ bool force;
+};
+
+static uint qHash(BreakAtKey const &);
+
+struct TextLayoutKey
+{
+ bool operator==(TextLayoutKey const & key) const {
+ return key.s == s && key.rtl == rtl && key.ws == ws;
+ }
+
+ docstring s;
+ bool rtl;
+ double ws;
+};
+
+
class GuiFontMetrics : public FontMetrics
{
public:
@@ -94,9 +120,9 @@ private:
/// Cache of string widths
mutable Cache<docstring, int> strwidth_cache_;
/// Cache for breakAt
- mutable Cache<docstring, std::pair<int, int>> breakat_cache_;
+ mutable Cache<BreakAtKey, std::pair<int, int>> breakat_cache_;
/// Cache for QTextLayout
- mutable Cache<docstring, std::shared_ptr<QTextLayout>> qtextlayout_cache_;
+ mutable Cache<TextLayoutKey, std::shared_ptr<QTextLayout>> qtextlayout_cache_;
struct AscendDescend {
int ascent;
diff --git a/src/support/Cache.h b/src/support/Cache.h
index 2b97f6e..1e44ff2 100644
--- a/src/support/Cache.h
+++ b/src/support/Cache.h
@@ -56,6 +56,8 @@ public:
return *obj;
return Val();
}
+ // Version that returns a pointer, and nullptr if the object is not present
+ Val * object_ptr(Key const & key) const { return Q::object(key); }
/// Synonymous for object, same remark as above.
Val operator[](Key const & key) const { return object(key); }
/// Everything from QCache except QCache::take.
More information about the lyx-cvs
mailing list