Perfomance: reuse compiled format templates
- Dominant language
- Python
- Stars
- 15.7k
- Forks
- 2.1k
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 31
Description
The question arose recently whether our funky AST compiler in the `functemplate` module is actually worth it. I wrote this compiler some time ago hoping for a dramatic speedup in `beet list` time, but I don't think I ever documented evidence either way about the speedup, and I had a vague inkling that it wasn't having the effect we wanted. Since it's giving @jrobeson trouble with the Python 3 port, I decided to investigate whether the code was worth keeping at all.
TL;DR: The compiler can yield a 1.6x speedup on my machine! But we're currently using it in a way that negates all the benefits.
I used `time beet ls > /dev/null` to measure the time it takes to list all the items in my 15265-track library on my really old Core 2 Duo server. The time was 41.5 ± 0.3 seconds (mean and standard error over 4 runs). Not great.
Then I tried disabling the compiler (the `functemplate` module also includes a straightforward interpreter). Shockingly, the time actually got slightly _better:_ 37.8 ± 0.3 seconds.
I investigated and found that, as you might have guessed, the code is written to invoke the compiler every time---so instead of compiling the template once, beets was compiling it 15 thousand times. :cry:
I patched the `list` command as a stopgap to use the compiler correctly:
``` diff
diff --git a/beets/ui/commands.py b/beets/ui/commands.py
index 867a473..7c5b454 100644
--- a/beets/ui/commands.py
+++ b/beets/ui/commands.py
@@ -1057,12 +1057,14 @@ def list_items(lib, query, album, fmt=''):
"""Print out items in lib matching query. If album, then search for
albums instead of single items.
"""
+ from beets.util.functemplate import Template
+ tmpl = Template(fmt)
if album:
for album in lib.albums(query):
- ui.print_(format(album, fmt))
+ ui.print_(album.evaluate_template(tmpl))
else:
for item in lib.items(query):
- ui.print_(format(item, fmt))
+ ui.print_(item.evaluate_template(tmpl))
```
and got much better time: 25.5 ± 0.2 seconds. Hot dang!
So the conclusions are:
- The compiler actually does address a meaningful bottleneck!
- We're completely missing out on the potential benefits!
- We need to reuse compiled templates, at least for the `list` command if not for all commands that use the standard formats from the config file.
---
Here are the raw timings:
``` sh
# Status quo.
$ time beet ls > /dev/null
beet ls > /dev/null 42.01s user 1.02s system 99% cpu 43.131 total
$ time beet ls > /dev/null
beet ls > /dev/null 41.83s user 1.02s system 99% cpu 42.961 total
$ time beet ls > /dev/null
beet ls > /dev/null 41.78s user 1.18s system 99% cpu 43.055 total
$ time beet ls > /dev/null
beet ls > /dev/null 40.72s user 1.27s system 99% cpu 42.114 total
# Compiler disabled.
$ time beet ls > /dev/null
beet ls > /dev/null 37.18s user 0.89s system 99% cpu 38.143 total
$ time beet ls > /dev/null
beet ls > /dev/null 37.45s user 1.10s system 99% cpu 38.781 total
$ time beet ls > /dev/null
beet ls > /dev/null 38.08s user 1.00s system 99% cpu 39.153 total
$ time beet ls > /dev/null
beet ls > /dev/null 38.33s user 1.12s system 99% cpu 39.527 total
# Compiler enabled, `list` command modified to actually pre-compile.
$ time beet ls > /dev/null
beet ls > /dev/null 25.54s user 1.06s system 99% cpu 26.671 total
$ time beet ls > /dev/null
beet ls > /dev/null 25.68s user 1.00s system 99% cpu 26.732 total
$ time beet ls > /dev/null
beet ls > /dev/null 25.75s user 0.95s system 99% cpu 26.745 total
$ time beet ls > /dev/null
beet ls > /dev/null 25.02s user 0.96s system 98% cpu 26.336 total
```
Contributor guide
Assessment
This issue has not been assessed yet.