-
-
Notifications
You must be signed in to change notification settings - Fork 383
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
PICARD-936: Improve UI responsiveness whilst busy loading, saving etc. #604
PICARD-936: Improve UI responsiveness whilst busy loading, saving etc. #604
Conversation
This is definitely better, however there are two threading issues that still need fixing:
Any suggestions welcome. |
picard/tagger.py
Outdated
get_files, | ||
process, | ||
priority=2, | ||
thread_pool=self.load_thread_pool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We run this on the load threadpool to avoid it blocking, and run it at priority 2 so that it runs ahead of queued tasks to load metadata from files.
But I wonder whether it might make more sense to have another threadpool just for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this and it did not seem to make much difference.
Whilst the UI is definitely a bit more responsive with the PR in its current state, the UI is still pretty unresponsive, and I am not sure how to proceed further. Any help gratefully received. |
16f8313
to
174a576
Compare
Ok - I have found the primary cause for UI non-responsiveness. In So I have added a That's the good news. The bad news is that it exposes other bug(s), most notably when loading a lot of files I get a slew of errors The UI is also too lively, showing updates to objects too fast for the eye to process - so we probably need to do something to only display them every so many miliseconds - and leaving some objects blank, So there is more work to do, but I am on the right track. |
Example recursion errors: picard-recursion-errors.txt |
Ok - I worked out that the cause was calling processEvents when we were on the main thread rather than leaving the main thread to get to it in its own time. So, basic functionality is now working great. I just need to see what tweaks I need to do to which functions run on which threads and this will be ready for wider testing and merge. |
At the current commit this PR seems stable - any further tweaks will be to threading to make the GUI even more responsive. So I would welcome anyone else giving this a good test - especially on non-windows systems - and to give feedback about whether it adversely impacts file load or save performance. |
P.S. Github is showing the last two commits in the wrong sequence if anyone is wondering about a missing import on the first one. |
Remote loads still leave UI unresponsive - need to agree with @samj1912 about borrowing some of his code to try to address this. |
An alt approach in #616 I am not sure which one will allow the UI to be more responsive. Can you check my PR's performance once too? We will go with the better alt. :) |
Since its a UI based thing, I just tested with a stopwatch from clicking the load button to when the UI is responsive again. I am running picard on Ubuntu 16.10, on a SSD , ext4. While loading ~1000 files using your code, it takes picard about ~25 seconds before it is responsive again. I think we can improve on this code further by finding where the UI is being blocked while loading and separating the load code from the main thread as much as possible :) |
I am working on the final piece - which I suspect is quite similar to what you have just done - in which case I will borrow your code if I may. I have just reworked the whole file loading code to eliminate duplications and prepare for running open_file in a thread which should make the UI immediately responsive. :-) |
Feel free to borrow my code if it helps :) 👍 |
Thank you. I did. The code in #616 was pretty much exactly what I was about to write - so this PR now includes both @samj1912's worker thread code and his Add Directory iteration rewrite - both with some restructuring to handle hidden and excluded files in the right place and also to facilitate a check on number of files to be added easily. See what you think. This PR is now ready for code review and testing by others on systems other than windows. In the mean while I will see whether this helps with slow-down when you have loaded large numbers of files. |
picard/tagger.py
Outdated
new_files.append(file) | ||
if new_files: | ||
new_files[filename] = file | ||
QtCore.QCoreApplication.processEvents() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment explaining this line.
picard/tagger.py
Outdated
for filename in filenames: | ||
filename = os.path.normpath(os.path.realpath(filename)) | ||
if ignore_hidden and is_hidden(filename): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this code block removed and moved to add directory code?
What if a user selects a group of files? Ignore regex and hidden file ignores won't work then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must have missed that. But it is an easy one to fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently NOT fixed. Sorry. Will fix it now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry again. It was fixed - but it is in _add_files_from_directory
lines 476-478 rather than here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samj1912 Since I have fixed this review comment can you please mark it as fixed?
The responsiveness of the UI is still not perfect - for example when loading files and processing webservice responses it can still be sluggish - however I doubt that there is much more that we can do with threading - I suspect that the cause is now the LGL preventing pre-emptive multi-tasking and that, If we can identify where to put them, a few QCoreApplication.processevent() calls will help further, Once this PR has been merged and I have the time I will take a look at some of these code paths and see if I can profile them. To put this in context, the UI unresponsiveness when loading local files is now of the order of a second or two rather than 10's of seconds i.e. an order of magnitude better. |
P.S. I have just finished loading 8594 pre-tagged files in 529 releases from a local drive and have not noticed any slowdown in file loading. It is early days for this code, but I am hopeful that the performance problems with large number of files have been significantly reduced. However, the handles leak still persists - at the end of this load, task manager reports Python as having 58,708 open handles!!!! To put this in context, after Picard has initialised completely, Python shows 755 handles. |
P.S. I am going on holiday next week for 2 weeks - so if you want code changes before I go away and before the release of 1.4 on 14 Feb, please let me have them in the next day or two at the latest. |
picard/tagger.py
Outdated
del self._cmdline_files | ||
if files: | ||
self.add_files([files]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't files
already a list here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add_files now takes a list of lists of filenames. We group files in each directory into a list and then create a list of these - so we can queue these for guess_format and loading on a directory by directory basis so the user does not have to wait until we have guess_format on every file they have selected to load before they start appearing in the UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P.S. About to rename tagger.py#add_files to _add_files and create a stub add_files to handle the Add Files dialog which @samj1912 picked up on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get why you need to pass list of lists of filenames, but i didn't check the code in details yet. I'm worried about such change concerning functions like tagger.add_files()
that may be used in plugins (we really need to clarify plugins API in future versions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been fixed. I renamed this to _add_files and created a stub tagger.add_files()
which provides the same interface and functionality as before.
Such PR will not be merged in 1.4 but rather in 1.4.1 |
No problem. |
picard/util/thread.py
Outdated
@@ -36,12 +36,15 @@ def run(self): | |||
|
|||
class Runnable(QRunnable): | |||
|
|||
def __init__(self, func, next): | |||
def __init__(self, func, next, os_priority=0): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is os_priority
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I accept that this is extra to the functionality of this PR. The idea is that it adjusts the operating system priority of the thread down (or up) in order to influence the way threads are dispatched by Python. But now I understand more about how the Python GIL works and impacts which thread gets the GIL (which is essentially random), I am not sure that it would be of any use. (And we don't know what else is running on the user's system and whether it would make sense to do it.)
So I will be happy to revert this Commit.
picard/util/thread.py
Outdated
QRunnable.__init__(self) | ||
self.func = func | ||
self.next = next | ||
self.thread_priority = min(QThread.IdlePriority,max(QThread.TimeCriticalPriority, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain in a comment please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will if we don't decide to revert this functionality.
picard/util/thread.py
Outdated
|
||
|
||
def to_main(func, *args, **kwargs): | ||
QCoreApplication.postEvent(QCoreApplication.instance(), | ||
ProxyToMainEvent(func, *args, **kwargs)) | ||
if QCoreApplication.instance().thread() != QThread.currentThread(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain in a comment please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
picard/tagger.py
Outdated
list_to_add = [] | ||
# walk topdown to allow modifying the sub-dirs to remove hidden dirs | ||
for root, dirs, files in os.walk(unicode(path), topdown=True): | ||
QtCore.QCoreApplication.processEvents() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line deserves a comment on its role.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
I think it will be better to remove the OS priority commits in this pr and make a separate pr for them. Also since you are rebasing would it be possible to cherry pick my commits to this PR so that the commits are kept. It will be nice if you could add me to collaborators for this pr, I might have a few changes I would like to make. |
To keep UI characteristics the same regardless of whether debug logging is off or on, if off we call processEvents.
... so that we can make UI more responsive by calling processEvents from a worker thread and avoid Recursion Depth Exceeded messages.
This enables file opening and loading to happen in parallel which gives a better UI experience.
1874c38
to
29c2554
Compare
There is still a problem with UI responsiveness loading large numbers of files. The symptoms are that one of the worker threads (doing the new file evaluation) monopolises execution despite issuing a processEvents. Debug messages from worker threads (which also run to_main) seem to appear promptly, but the call-back function doesn't run promptly. Other worker threads like loading files are not getting a look in and I suspect that neither is the main thread. I am guessing that when it issues a local disk I/O for the 128 bytes the read finishes so quickly that the thread is ready to execute when Python issues a wakeup to all threads to decide what to execute next - and when the thread runs to_main and processEvents, again the thread is ready to run. This may be because processEvents only runs events posted from the worker thread (as suggested by the documentation). Or it may be simply because Python doesn't handle threading well and the same thread gets the execution back again by accident (putting work into worker threads typically slows things down - see presentation on how the GIL works). So moving some code back to main thread might actually improve things. Or perhaps there is some way to force the open_threadpool to give up CPU and allow other threads to execute (we could use time.sleep but that introduces delays when there is no other work). This is likely to be a bit trial and error (unless anyone else has a bright idea that could help), so please bear with me. |
Ok - I am getting there. However, I could do with a bit of advice. According to http://www.qtcentre.org/printthread.php?t=7167&pp=100 we should be using signals and slots to do calling between worker threads and main thread. I haven't tried switching yet, but it looks like this switches thread and executes the slot immediately - and if so that would seem to be a better way of doing I am wondering whether our current approach in |
processEvents processes events in the running THREAD'S event queue not in the main thread. By using msleep(10) we give up CPU to allow main thread to grab it.
Ok - This is ready for further testing. Please let me know if I have not resolved any previous review comments. |
@Sophist-UK build is failing. Lots of attribute errors. |
@samj1912 Damn. It was running perfectly on my PC. But I didn't run unit tests as I should have done - rusty at this. And when I do, I get the same errors. I will look into it. |
I suspect that this is more about how unit testing is setup - without Qt initialisation to_main won't work. So I either need to modify setup.py so that it initialises Qt (prefered) or have additional checks in thread.py to handle unit test situations (which I don't like the idea of). |
This is needed for unit tests where it is not initialised
…://github.com/Sophist-UK/Sophist_picard into PICARD-936_separate_threadpool_for_file_loads
Hopefully that will fix it - will check that Travis ran clean in the morning. |
Still more work needed.
|
It's been almost 8 months since this was updated. Closing this with a 'Needs more work' tag. You can reopen this if you want to continue working on it. Also we have updated to Qt5 and py3 so be mindful of that while reworking on this PR. |
I agree that this should be closed for three reasons: a. It was written for P1.4 and needs to be retrofitted to P2. When I get time I will rework it for P2 in smaller chunks. |
Resolves PICARD-936.
Improves the responsiveness during worker thread operations by triggering main thread to process events after events are sent to it.
Improves file loading performance by moving operations into separate threadpools.
This PR was brought to you as a joint effort between Sophist-UK and @samj1912.