beetbox / beetbox/beets

Perfomance: reuse compiled format templates

Open
#2,030 4 comments 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.