[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