diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py
index 97a4a7ce31..3195b52c9d 100755
--- a/beets/dbcore/db.py
+++ b/beets/dbcore/db.py
@@ -25,7 +25,7 @@
 import contextlib
 
 import beets
-from beets.util.functemplate import Template
+from beets.util import functemplate
 from beets.util import py3_path
 from beets.dbcore import types
 from .query import MatchQuery, NullSort, TrueQuery
@@ -597,7 +597,7 @@ def evaluate_template(self, template, for_path=False):
         """
         # Perform substitution.
         if isinstance(template, six.string_types):
-            template = Template(template)
+            template = functemplate.template(template)
         return template.substitute(self.formatted(for_path),
                                    self._template_funcs())
 
diff --git a/beets/library.py b/beets/library.py
index d49d672271..5786ce9d97 100644
--- a/beets/library.py
+++ b/beets/library.py
@@ -31,7 +31,7 @@
 from beets import util
 from beets.util import bytestring_path, syspath, normpath, samefile, \
     MoveOperation
-from beets.util.functemplate import Template
+from beets.util.functemplate import template, Template
 from beets import dbcore
 from beets.dbcore import types
 import beets
@@ -855,7 +855,7 @@ def destination(self, fragment=False, basedir=None, platform=None,
         if isinstance(path_format, Template):
             subpath_tmpl = path_format
         else:
-            subpath_tmpl = Template(path_format)
+            subpath_tmpl = template(path_format)
 
         # Evaluate the selected template.
         subpath = self.evaluate_template(subpath_tmpl, True)
@@ -1134,7 +1134,7 @@ def art_destination(self, image, item_dir=None):
         image = bytestring_path(image)
         item_dir = item_dir or self.item_dir()
 
-        filename_tmpl = Template(
+        filename_tmpl = template(
             beets.config['art_filename'].as_str())
         subpath = self.evaluate_template(filename_tmpl, True)
         if beets.config['asciify_paths']:
diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py
index 327db6b044..622a1e7f01 100644
--- a/beets/ui/__init__.py
+++ b/beets/ui/__init__.py
@@ -36,7 +36,7 @@
 from beets import library
 from beets import plugins
 from beets import util
-from beets.util.functemplate import Template
+from beets.util.functemplate import template
 from beets import config
 from beets.util import confit, as_string
 from beets.autotag import mb
@@ -616,7 +616,7 @@ def get_path_formats(subview=None):
     subview = subview or config['paths']
     for query, view in subview.items():
         query = PF_KEY_QUERIES.get(query, query)  # Expand common queries.
-        path_formats.append((query, Template(view.as_str())))
+        path_formats.append((query, template(view.as_str())))
     return path_formats
 
 
diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py
index 6a34a3bb38..9c625b7f98 100644
--- a/beets/util/functemplate.py
+++ b/beets/util/functemplate.py
@@ -35,6 +35,7 @@
 import types
 import sys
 import six
+import functools
 
 SYMBOL_DELIM = u'$'
 FUNC_DELIM = u'%'
@@ -553,8 +554,20 @@ def _parse(template):
     return Expression(parts)
 
 
-# External interface.
+# Decorator that enables lru_cache on py3, and no caching on py2.
+def cached(func):
+    if six.PY2:
+        # Sorry python2 users, no caching for you :(
+        return func
+    return functools.lru_cache(maxsize=128)(func)
+
 
+@cached
+def template(fmt):
+    return Template(fmt)
+
+
+# External interface.
 class Template(object):
     """A string template, including text, Symbols, and Calls.
     """
diff --git a/docs/changelog.rst b/docs/changelog.rst
index d88bf0b12b..085345353a 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -117,6 +117,9 @@ Some improvements have been focused on improving beets' performance:
   to be displayed.
   Thanks to :user:`pprkut`.
   :bug:`3089`
+* Querying the library was further improved by reusing compiled teamplates
+  instead of compiling them over and over again.
+  Thanks to :user:`SimonPersson`.
 * :doc:`/plugins/absubmit`, :doc:`/plugins/badfiles`: Analysis now works in
   parallel (on Python 3 only).
   Thanks to :user:`bemeurer`.