-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Performance: Common operations are slow. #2388
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hi! Thanks for looking into this—we could really use some help with digging into performance issues exactly like this. I also agree that starting with
In each of these cases, we could make better choices about where to be lazy and where to be eager. In particular, one big win might be to fetch all the flexible attributes at once using a hefty I hope that helps! I'm happy to explain other stuff in more detail too if it would be helpful. But even just doing this profiling work to find the bottlenecks in As an aside, I recently discovered this tool while doing a little profiling work: It renders a nice interactive, graphical representation of Python profiling data. |
It helps a lot! Thank you. My initial attempt was going to be to just make everything eager and see what that does. This would mean storing a lot more stuff in memory, but minimizing the number of db calls that have to be made. The main problem with this is that it makes me start to worry about memory consumption for particularly large libraries, but I'm not sure how to avoid that without trying to restructure everything to be a streaming operation, and I certainly don't want to do something that drastic. The next step would be to try to make the db calls more clever so that it only eagerly pulls out what data is actually necessary for whatever the user is trying to do, a la those There might be a few pure-code things that can be done as well but database will definitely be the low-hanging fruit. I'll try out snakeviz, I've never seen it before! Thanks. |
we're also missing indexes. |
Initial impressions:
Some poking has made listing items 60% faster (on my machine), mostly by taking things away. See icefoxen@d12a1d2 . However it also breaks a lot of things, so no pull request yet. |
Initial responses:
Anyway, as you say, it would be interesting to explore a version that does everything eagerly and start from there. That change could be easily confined to the way we construct |
Eagerly constructing |
Got it, that makes sense. So you're saying, for example, that
and just issue the query I totally agree that this would be the ideal speed. The game, of course, is finding a way to do that—or something close to it—while maintaining abstractions. I don't have any great ideas, but here are some bad ideas:
|
Not really. Abstraction is good. But each database call has quite a lot of overhead compared to the amount of actual work done, and we do multiple database calls to fetch all the information required for each item. The cost of actually constructing the Item and Album objects is... well, not quite trivial, but very small in comparison. We need an easier way to handle Items and Albums in batches, so that we do the same work with fewer calls into the database. Right now if you want to construct 1000 |
Yep, that sounds right. Maybe a useful exercise would be to hand-write the queries we wish we were executing, and then design a usable Python API around that? |
I don't know if my issue belongs in a separate issue from this or not. It may fall under general performance just like this issue. ProblemSame setup as #2446. Different issue: if a user wants to look up a giant boxset, it shouldn't take as long as it does. Is there any kind of optimization or parallelization that can be done? My CPU only has 20% use while this operation runs. What is beets actually doing when it's stuck looking up a big boxset? From my verbose log, it looks like it's calculating distance between the local files and candidate albums on the web. |
I do not code and so I am not the best person to enter in this discussion, but based on my experience in compiling and testing stuff I guess the main reason of the beets slowness is the fact it is fully written in python. If beets itself was written using some compiled language (C, C++, ADA etc) and then make use of the existent python libraries (py-musicbrainzngs, py-pyacoustid etc) it certainly would be rather faster. While comparing apples and oranges, Picard is moderately fast but slow when performing actions what depend on (more or less) the same python libraries Beets does depend. I am using beets for a while and I really enjoy it, but I was shocked when it took more the 36 hours to import a Maria Callas box with about 1400 tracks. :-O It take about 2 seconds just to display "beet -h" in here. :-) Just my 2c. |
That's a good idea, but porting some or most of the code to a compiled language would be a huge undertaking. Similar gains may be possible just by optimizing the python code. |
The issue isn't with Python, the issue is with beets falling prey to the classic N+1 SELECT problem:
The clever way to do such a series of queries would be to group all of the ids for each successive query and then join them together into a single So if the old way was, I don't know anything about how beets works under the hood, so I don't know if that's easily done with the Object model y'all are working with, but it's a well-trod problem and can be done. |
@NoahTheDuke Yeah that's more or less the problem, as I recall it. It was just too large a problem for me to be able to handle, since it seemed to me that it would take a substantial amount of redesigning to fix. |
Importing a parent folder with many albums vs "loop items in parentfolder and invoke beet import on every subfolder / album" improves the import time immensely. In fact, that's the only way I'm able to bulk import big sets (I just use a simple bash one liner). So I'm guessing the biggest gain will come from reflecting such invocation in the code. |
I decided to look into this a bit today. Beets is not painfully slow for me, but still quite a bit slower than it should be. @NoahTheDuke: I disagree that I imagine that the indices in the DB where created after this issue was opened, because now the sqlite calls do not take a lot of time according to the profiler. Running
This brings the runtime down to 4 seconds. At this point, the output of
It seems like we spend 2 seconds translating db records to models ( I haven't yet looked into how much effort it would be to optimize these two functions (printing, and model creation), but it seems realistic to get |
Awesome question! This is a super useful investigation. I am particularly interested that you found such a high cost for formatting. Some time ago, I did a somewhat ridiculous thing to try to optimize template formatting—I wrote a compiler that translates template strings into proper Python functions for evaluation. After I did so, I vaguely remember concluding that it didn't have a large impact—and I thought it was because template formatting didn't matter all that much. And perhaps it doesn't—the problem might be formatting-adjacent—but it really looks like this piece of the puzzle is worth a closer look. |
Thanks for the hint! It looks like the template is actually recompiled over and over, which you actually discovered yourself in #2030 some time ago :) As an experiment, I applied the following patch:
With this, |
I made investigated the templating logic further today, and created PR #3258 with my findings. |
I am shocked but not surprised at my terrible memory. 😃 Thank you for continuing this investigation!! 🚀 |
Most notoriously Weezer have a lot of albums with the same name. Two of them were even released in the same year, so the year would not be enough to tell them apart. As far as I could tell there is no field available through MPD that would fix this. Unfortunately beets lookups are very slow, so there is a delay both spawning the menu and especially adding the album. This could be solved by keeping our own copy of the beets database (probably best kept up-to-date by a beets plugin). Ideally it would be solved in beets, but there seems to have been no progress on this in many years (beetbox/beets#2388).
Just started using beets and I love it, but holy cats is it slow sometimes, even for very simple things like simple queries and listings. I only have ~10k songs and it's still rather a pain.
So, I'd like to make it faster, and welcome advice on how to do so. The first suspect is database calls. Compare how long it takes to list items for even a small library, vs. just reading the information from the database:
(Also I'm not sure why my library of 385 songs has 11k items in it; that's something else to investigate.)
Some profiling suggests that this is a likely candidate:
So yes, it does spend most of its time talking to the DB, and adding more items to the library seems to add 3 more
sqlite3.Connection.execute()
calls per library item. So, the first goal is to do more intelligent batching of database calls. If I can make ls faster, then at best it will make a lot of other things faster as well, and at worst I'll be able to start attacking other problems.Other performance issues to look into: #2370 #2281 #1795
Setup
The text was updated successfully, but these errors were encountered: