The attached patch implements a cache around the function
GuiFontMetrics::breakAt, which has been identified as very slow by
Guillaume. To this end, I use a plain QCache object.
This patch is also instrumented using pmprof.h. When I load the
UserGuide, and move along the document using Cursor-Down, I get the
following results:
* Intel(R) Xeon(R) CPU E5-1620 v3 @ 3.50GHz, ubuntu 12.04, Qt 4.8.1
#pmprof# breakAt: 20.69usec, count=1651, total=34.16msec
hit: 63%, 3.54usec, count=1052, total=3.72msec
miss: 36%, 50.82usec, count=599, total=30.44msec
* Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz, ubuntu 16.04, Qt 4.8.7
#pmprof# breakAt: 221.85usec, count=4884, total=1083.54msec
hit: 83%, 2.29usec, count=4063, total=9.31msec
miss: 16%, 1308.43usec, count=821, total=1074.22msec
* Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz, ubuntu 16.04, Qt 5.5.1
#pmprof# breakAt: 4.20usec, count=16820, total=70.56msec
hit: 96%, 1.44usec, count=16267, total=23.43msec
miss: 3%, 85.23usec, count=553, total=47.13msec
One can see that the improvement between the hit and miss branches is
x500 with Qt 4.8.7, but only x14 with Qt 4.8.1 and x60 with Qt 5.5.1.
Note however that the miss path is much faster with Qt5 in the last two
examples (same machine).
Comments welcome. My plan is to use that for both Qt4 and Qt5 (for
simplicity). Seeing the different behavior of Qt 4.8.1 and 4.8.7 (albeit
different machines), I suspect that the problem is elsewhere in the
configuration.
I would be glad to have numbers from your on systems.
JMarc
>From 6953f4c7d95f27d6bf7869bbdc7d94f4b6c4cb66 Mon Sep 17 00:00:00 2001
From: Jean-Marc Lasgouttes <lasgout...@lyx.org>
Date: Tue, 5 Jul 2016 14:06:22 +0200
Subject: [PATCH] Add a cache for GuiFontMetrics::breakAt
The QTextLayout handling is terribly slow on Qt 4.8.7, but some
caching has been added in Qt5 that makes it much faster. For some
reason, it is not that slow with Qt 4.8.1.
This patch adds some caching in the breakAt method, the only user of
QTextLayout which did not have some kind of caching already. It should
probably only be active for Qt 4.
---
src/frontends/qt4/GuiFontMetrics.cpp | 46 ++++++++++++++++++++++++++--------
src/frontends/qt4/GuiFontMetrics.h | 6 +++++
2 files changed, 41 insertions(+), 11 deletions(-)
diff --git a/src/frontends/qt4/GuiFontMetrics.cpp b/src/frontends/qt4/GuiFontMetrics.cpp
index eade8cc..2368e8c 100644
--- a/src/frontends/qt4/GuiFontMetrics.cpp
+++ b/src/frontends/qt4/GuiFontMetrics.cpp
@@ -22,6 +22,7 @@
#include "insets/Inset.h"
#include "support/lassert.h"
+#include "support/pmprof.h"
using namespace std;
using namespace lyx::support;
@@ -51,10 +52,10 @@ inline QChar const ucs4_to_qchar(char_type const ucs4)
} // anon namespace
-// Limit strwidth_cache_ size to 512kB of string data
+// Limit (strwidth|breakat)_cache_ size to 512kB of string data
GuiFontMetrics::GuiFontMetrics(QFont const & font)
: font_(font), metrics_(font, 0),
- strwidth_cache_(1 << 19),
+ strwidth_cache_(1 << 19), breakat_cache_(1 << 19),
tl_cache_rtl_(false), tl_cache_wordspacing_(-1.0)
{
}
@@ -214,10 +215,9 @@ int GuiFontMetrics::x2pos(docstring const & s, int & x, bool const rtl,
}
-bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const force) const
+pair<int, int> GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
+ bool const rtl, bool const force) const
{
- if (s.empty())
- return false;
QTextLayout tl;
/* Qt will not break at a leading or trailing space, and we need
* that sometimes, see http://www.lyx.org/trac/ticket/9921.
@@ -225,7 +225,7 @@ bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const
* To work around the problem, we enclose the string between
* zero-width characters so that the QTextLayout algorithm will
* agree to break the text at these extremal spaces.
- */
+ */
// Unicode character ZERO WIDTH NO-BREAK SPACE
QChar const zerow_nbsp(0xfeff);
tl.setText(zerow_nbsp + toqstr(s) + zerow_nbsp);
@@ -240,11 +240,36 @@ bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const
line.setLineWidth(x);
tl.createLine();
tl.endLayout();
- if ((force && line.textLength() == 1) || int(line.naturalTextWidth()) > x)
- return false;
- x = int(line.naturalTextWidth());
// The -1 is here to account for the leading zerow_nbsp.
- s = s.substr(0, line.textLength() - 1);
+ return make_pair(line.textLength() - 1, int(line.naturalTextWidth()));
+}
+
+
+bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const force) const
+{
+ PROFILE_THIS_BLOCK(breakAt)
+ if (s.empty())
+ return false;
+ docstring s_cache = s + (rtl ? "r" : "l") + (force ? "f" : "w");
+
+ QByteArray qba =
+ QByteArray(reinterpret_cast<char const *>(s_cache.data()),
+ s.size() * sizeof(docstring::value_type));
+ pair<int, int> * pp = breakat_cache_[qba];
+ int len = 0;
+ int newx = 0;
+ if (pp)
+ tie(len, newx) = *pp;
+ else {
+ PROFILE_CACHE_MISS(breakAt)
+ tie(len, newx) = breakAt_helper(s, x, rtl, force);
+ breakat_cache_.insert(qba, new pair<int, int>(len, newx), qba.size());
+ }
+
+ if ((force && len == 0) || newx > x)
+ return false;
+ x = newx;
+ s = s.substr(0, len);
return true;
}
@@ -260,7 +285,6 @@ void GuiFontMetrics::rectText(docstring const & str,
}
-
void GuiFontMetrics::buttonText(docstring const & str,
int & w, int & ascent, int & descent) const
{
diff --git a/src/frontends/qt4/GuiFontMetrics.h b/src/frontends/qt4/GuiFontMetrics.h
index 8c8daab..de000a1 100644
--- a/src/frontends/qt4/GuiFontMetrics.h
+++ b/src/frontends/qt4/GuiFontMetrics.h
@@ -69,6 +69,9 @@ private:
getTextLayout(docstring const & s, QFont font,
bool const rtl, double const wordspacing) const;
+ std::pair<int, int> breakAt_helper(docstring const & s, int const x,
+ bool const rtl, bool const force) const;
+
/// The font
QFont font_;
@@ -81,6 +84,9 @@ private:
/// Cache of string widths
mutable QCache<QByteArray, int> strwidth_cache_;
+ /// Cache for breakAt
+ mutable QCache<QByteArray, std::pair<int, int>> breakat_cache_;
+
struct AscendDescend {
int ascent;
int descent;
--
1.7.9.5