From 9915b6581dd458658267b1623d6ce09832dd7db6 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 19 May 2014 13:03:33 -0400 Subject: [PATCH 1/3] Don't store cache lookups in the file. Invalid the cache when font family parameters change. --- lib/matplotlib/font_manager.py | 58 ++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 9bc5ee9ee712..04f88202461d 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -952,7 +952,47 @@ def pickle_load(filename): data = pickle.load(fh) return data -class FontManager: + +class TempCache(object): + """ + A class to store temporary caches that are (a) not saved to disk + and (b) invalidated whenever certain font-related + rcParams---namely the family lookup lists---are changed or the + font cache is reloaded. This avoids the expensive linear search + through all fonts every time a font is looked up. + """ + # A list of rcparam names that, when changed, invalidated this + # cache. + invalidating_rcparams = [ + 'font.serif', 'font.sans-serif', 'font.cursive', 'font.fantasy', + 'font.monospace'] + + def __init__(self): + self._lookup_cache = {} + self._last_rcParams = self.make_rcparams_key() + + def make_rcparams_key(self): + key = [id(fontManager)] + for param in self.invalidating_rcparams: + key.append(rcParams[param]) + return key + + def get(self, prop): + key = self.make_rcparams_key() + if key != self._last_rcParams: + self._lookup_cache = {} + self._last_rcParams = key + return self._lookup_cache.get(prop) + + def set(self, prop, value): + key = self.make_rcparams_key() + if key != self._last_rcParams: + self._lookup_cache = {} + self._last_rcParams = key + self._lookup_cache[prop] = value + + +class FontManager(object): """ On import, the :class:`FontManager` singleton instance creates a list of TrueType fonts based on the font properties: name, style, @@ -1015,9 +1055,6 @@ def __init__(self, size=None, weight='normal'): else: self.defaultFont['afm'] = None - self.ttf_lookup_cache = {} - self.afm_lookup_cache = {} - def get_default_weight(self): """ Return the default font weight. @@ -1200,15 +1237,13 @@ def findfont(self, prop, fontext='ttf', directory=None, return fname if fontext == 'afm': - font_cache = self.afm_lookup_cache fontlist = self.afmlist else: - font_cache = self.ttf_lookup_cache fontlist = self.ttflist if directory is None: - cached = font_cache.get(hash(prop)) - if cached: + cached = _lookup_cache[fontext].get(prop) + if cached is not None: return cached best_score = 1e64 @@ -1266,7 +1301,7 @@ def findfont(self, prop, fontext='ttf', directory=None, raise ValueError("No valid font could be found") if directory is None: - font_cache[hash(prop)] = result + _lookup_cache[fontext].set(prop, result) return result @@ -1348,6 +1383,11 @@ def findfont(prop, fontext='ttf'): fontManager = None + _lookup_cache = { + 'ttf': TempCache(), + 'afm': TempCache() + } + def _rebuild(): global fontManager fontManager = FontManager() From ecca9c31d935040071251fc181915d58298a6be3 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 19 May 2014 14:14:54 -0400 Subject: [PATCH 2/3] Don't explicitly rebuild font cache in test. --- lib/matplotlib/tests/test_font_manager.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index c55be8846759..df2675219023 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -6,7 +6,7 @@ import os -from matplotlib.font_manager import findfont, FontProperties, _rebuild +from matplotlib.font_manager import findfont, FontProperties from matplotlib import rc_context @@ -14,10 +14,6 @@ def test_font_priority(): with rc_context(rc={ 'font.sans-serif': ['cmmi10', 'Bitstream Vera Sans']}): - # force the font manager to rebuild it self - _rebuild() font = findfont( FontProperties(family=["sans-serif"])) assert_equal(os.path.basename(font), 'cmmi10.ttf') - # force it again - _rebuild() From 0f25c750136c8e1d620294d98ba2010a21f350c5 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 19 May 2014 17:01:03 -0400 Subject: [PATCH 3/3] Modest performance improvement. --- lib/matplotlib/font_manager.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 04f88202461d..b6882826a17e 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -963,19 +963,17 @@ class TempCache(object): """ # A list of rcparam names that, when changed, invalidated this # cache. - invalidating_rcparams = [ + invalidating_rcparams = ( 'font.serif', 'font.sans-serif', 'font.cursive', 'font.fantasy', - 'font.monospace'] + 'font.monospace') def __init__(self): self._lookup_cache = {} self._last_rcParams = self.make_rcparams_key() def make_rcparams_key(self): - key = [id(fontManager)] - for param in self.invalidating_rcparams: - key.append(rcParams[param]) - return key + return [id(fontManager)] + [ + rcParams[param] for param in self.invalidating_rcparams] def get(self, prop): key = self.make_rcparams_key()