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.
This commit is contained in:
Jean-Marc Lasgouttes 2021-09-24 16:57:05 +02:00
parent 9ae002b69f
commit 6bbd88accf
3 changed files with 52 additions and 14 deletions

View File

@ -253,8 +253,8 @@ int GuiFontMetrics::rbearing(char_type c) const
int GuiFontMetrics::width(docstring const & s) const int GuiFontMetrics::width(docstring const & s) const
{ {
PROFILE_THIS_BLOCK(width); PROFILE_THIS_BLOCK(width);
if (strwidth_cache_.contains(s)) if (int * wid_p = strwidth_cache_.object_ptr(s))
return strwidth_cache_[s]; return *wid_p;
PROFILE_CACHE_MISS(width); PROFILE_CACHE_MISS(width);
/* Several problems have to be taken into account: /* Several problems have to be taken into account:
* * QFontMetrics::width does not returns a wrong value with Qt5 with * * 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> shared_ptr<QTextLayout const>
GuiFontMetrics::getTextLayout(docstring const & s, bool const rtl, GuiFontMetrics::getTextLayout(docstring const & s, bool const rtl,
double const wordspacing) const double const wordspacing) const
{ {
PROFILE_THIS_BLOCK(getTextLayout); PROFILE_THIS_BLOCK(getTextLayout);
docstring const s_cache = TextLayoutKey key{s, rtl, wordspacing};
s + (rtl ? "r" : "l") + convert<docstring>(wordspacing); if (auto ptl = qtextlayout_cache_[key])
if (auto ptl = qtextlayout_cache_[s_cache])
return ptl; return ptl;
PROFILE_CACHE_MISS(getTextLayout); PROFILE_CACHE_MISS(getTextLayout);
auto const ptl = make_shared<QTextLayout>(); auto const ptl = make_shared<QTextLayout>();
@ -368,7 +373,7 @@ GuiFontMetrics::getTextLayout(docstring const & s, bool const rtl,
ptl->beginLayout(); ptl->beginLayout();
ptl->createLine(); ptl->createLine();
ptl->endLayout(); ptl->endLayout();
qtextlayout_cache_.insert(s_cache, ptl); qtextlayout_cache_.insert(key, ptl);
return 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 bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const force) const
{ {
PROFILE_THIS_BLOCK(breakAt); PROFILE_THIS_BLOCK(breakAt);
if (s.empty()) if (s.empty())
return false; return false;
docstring const s_cache = BreakAtKey key{s, x, rtl, force};
s + convert<docstring>(x) + (rtl ? "r" : "l") + (force ? "f" : "w");
pair<int, int> pp; pair<int, int> pp;
if (auto * pp_ptr = breakat_cache_.object_ptr(key))
if (breakat_cache_.contains(s_cache)) pp = *pp_ptr;
pp = breakat_cache_[s_cache];
else { else {
PROFILE_CACHE_MISS(breakAt); PROFILE_CACHE_MISS(breakAt);
pp = breakAt_helper(s, x, rtl, force); 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) if (pp.first == -1)
return false; return false;

View File

@ -27,6 +27,32 @@
namespace lyx { namespace lyx {
namespace frontend { 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 class GuiFontMetrics : public FontMetrics
{ {
public: public:
@ -94,9 +120,9 @@ private:
/// Cache of string widths /// Cache of string widths
mutable Cache<docstring, int> strwidth_cache_; mutable Cache<docstring, int> strwidth_cache_;
/// Cache for breakAt /// Cache for breakAt
mutable Cache<docstring, std::pair<int, int>> breakat_cache_; mutable Cache<BreakAtKey, std::pair<int, int>> breakat_cache_;
/// Cache for QTextLayout /// Cache for QTextLayout
mutable Cache<docstring, std::shared_ptr<QTextLayout>> qtextlayout_cache_; mutable Cache<TextLayoutKey, std::shared_ptr<QTextLayout>> qtextlayout_cache_;
struct AscendDescend { struct AscendDescend {
int ascent; int ascent;

View File

@ -56,6 +56,8 @@ public:
return *obj; return *obj;
return Val(); 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. /// Synonymous for object, same remark as above.
Val operator[](Key const & key) const { return object(key); } Val operator[](Key const & key) const { return object(key); }
/// Everything from QCache except QCache::take. /// Everything from QCache except QCache::take.