The Qt5Agg backend is extremely slow when panning a plot. When continuously moving the mouse the refresh rates go down further. At some point it takes multiple seconds for the plot to redraw, leaving the user interface completely unresponsive. On the same machine the Qt4Agg backend works fine in comparison.
import matplotlib
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
plt.plot(np.range(5))
plt.show()
The Qt5Agg backend is extremely slow when panning a plot. When
continuously moving the mouse the refresh rates go down further. At some
point it takes multiple seconds for the plot to redraw, leaving the user
interface completely unresponsive. On the same machine the Qt4Agg backend
works fine in comparison.
import matplotlib
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
plt.plot(np.range(5))
plt.show()
—
Reply to this email directly or view it on GitHub #4604.
Tested on matplotlib master, but I've experienced this problem since the introduction of Qt5Agg. I just didn't depend on Qt5 back then and decided to wait until things might get more stable.
Also the slow panning behaviour applies to imshow plots and simple line plots alike.
Tested on matplotlib master, but I've experienced this problem since the
introduction of Qt5Agg. I just didn't depend on Qt5 back then and decided
to wait until things might get more stable.
Also the slow panning behaviour applies to imshow plots and simple line
plots alike.
—
Reply to this email directly or view it on GitHub #4604 (comment)
.
From a quick glance (if I'm getting the cProfile output right), the runtime of backend_bases.py:2989(drag_pan) on Qt4 is 1.695s, while Qt5 takes 5.690s and is called 4 times as much. Same proportions with backend_qt5.py:668(dynamic_update) and backend_qt5agg.py:140(draw).
However, I'm still missing a measure of how many frames are actually seen on display. It looks like on Qt5Agg the screen was updated no more than 3 times at all.
Just to show you how desperate I am, this is (another run) to show how these 200+ total redraws look like: http://youtu.be/1WAJp5ett9M
That is 8 seconds until the last frame is drawn. Also Ubuntu, but different machine and different graphics driver.
Thats definitely bad. I still can't really reproduce it (OSX and Ubuntu 15.04 virtual machine). Does anyone have any idea what controls the event rate?
It seems that (on systems I tested) Qt5 emits much more events leading to more FigureCanvasQt draw() calls. Ideally, a received event should notify Qt to do a deferred (costly) update the scene/figure. The current implementation however redraws the Agg canvas for every single event.
def draw(self):
"""
Draw the figure with Agg, and queue a request
for a Qt draw.
"""
# The Agg draw is done here; delaying it until the paintEvent
# causes problems with code that uses the result of the
# draw() to update plot elements.
FigureCanvasAgg.draw(self)
self._priv_update()
When I move the FigureCanvasAgg.draw() call to the paintEvent I'm getting excellent performance. @mfitzp states that matplotlib requires a draw call at this time since other code might rely on the fact that draw() implicitly updates plot elements too, like tick label positions. When writing the PGF backend I experienced a similar dilemma and ended up implementing a dummy flag for ignoring any drawing calls to the renderer when calling draw() isn't actually used for drawing. Maybe it is possible to add something like this to FigureCanvasAgg as well.
The way draw() is designed/used in matplotlib leads to very cumbersome handling within the backends, especially when the actual drawing process is very expensive and spurious redraws must be avoided at all costs. Matplotlib should have a method like recalculate() instead of using draw() for updating plot elements.
So apparently the draw_idle and dynamic_update canvas methods are supposed to be called for notifying a GUI to repaint itself. For GUI event handlers I replaced all the synchronous draw calls with the asynchronous draw_idle methods (proposed fix: b87bd20). As far as I can see this completely fixes the problem for me. Any regressions on other platforms or comments concerning the modifications?
So apparently the draw_idle and dynamic_update canvas methods are
supposed to be called for notifying a GUI to repaint itself. For GUI event
handlers I replaced all the synchronous draw calls with the asynchronous
draw_idle methods (proposed fix: b87bd20 b87bd20).
As far as I can see this completely fixes the problem for me. Any
regressions on other platforms or comments concerning the modifications?
—
Reply to this email directly or view it on GitHub #4604 (comment)
.
Yea, I must confess I don't understand why draw_idle was made that way. If I got the idea right, draw_idle should post a single redraw request to the event loop and prevent flooding the event loop with further requests as long as the outstanding draw wasn't dealt with. That is exactly what QWidget.update() is for, no need to reinvent it.
By the way, it would be interesting to check if this _priv_update() hack is still necessary on Qt5/Win. Did draw_idle emulate the intended behaviour of update() because it did (does?) not work on Windows?
I'm currently on holiday in Greece so won't be able to look at this til
next Wed.
For what it's worth I suspect it may be occurring due to Qt5 support for
high-resolution trackpads (and multi touch). This means Qt5 produces a lot
more events per scroll than Qt4.
Activity
WeatherGod commented on Jul 8, 2015
Just to help work this out, what version of matplotlib? Also, is this
problem irrespective of whether one is plotting an image versus a line plot?
On Wed, Jul 8, 2015 at 11:13 AM, Peter Würtz notifications@github.com
wrote:
pwuertz commented on Jul 8, 2015
Tested on matplotlib master, but I've experienced this problem since the introduction of Qt5Agg. I just didn't depend on Qt5 back then and decided to wait until things might get more stable.
Also the slow panning behaviour applies to imshow plots and simple line plots alike.
jenshnielsen commented on Jul 8, 2015
I cannot reproduce this with:
PyQt 5.4.2 (Homebrew)
Qt 5.4.2 (Homebrew)
on OS X 10.10.3
pwuertz commented on Jul 8, 2015
I think I saw this on every Ubuntu system so far. Someone who can confirm this?
WeatherGod commented on Jul 8, 2015
It would be interesting to do some profiling to see if there are some
extraneous draw calls happening. It happened once to me in mplot3d.
On Wed, Jul 8, 2015 at 11:56 AM, Peter Würtz notifications@github.com
wrote:
jenshnielsen commented on Jul 8, 2015
I will try to test it out on a Ubuntu system when I have a chance
pwuertz commented on Jul 8, 2015
Here is a quick cProfile for panning a plot by moving the mouse for roughly 2 seconds, then stop and trying to close the window.
https://gist.github.com/pwuertz/8046f7092871046f3e50#file-profile_qt4agg-txt
https://gist.github.com/pwuertz/8046f7092871046f3e50#file-profile_qt5agg-txt
From a quick glance (if I'm getting the cProfile output right), the runtime of backend_bases.py:2989(drag_pan) on Qt4 is 1.695s, while Qt5 takes 5.690s and is called 4 times as much. Same proportions with backend_qt5.py:668(dynamic_update) and backend_qt5agg.py:140(draw).
jenshnielsen commented on Jul 8, 2015
From my reading it looks like drag_pan is indeed called 4 times as often in the qt5 backend. The per_call time is almost the same. (0.024 vs 0.025 )
pwuertz commented on Jul 8, 2015
However, I'm still missing a measure of how many frames are actually seen on display. It looks like on Qt5Agg the screen was updated no more than 3 times at all.
jenshnielsen commented on Jul 8, 2015
As far as I can se it's calling figure draw 69 vs 242 times
pwuertz commented on Jul 8, 2015
Just to show you how desperate I am, this is (another run) to show how these 200+ total redraws look like: http://youtu.be/1WAJp5ett9M
That is 8 seconds until the last frame is drawn. Also Ubuntu, but different machine and different graphics driver.
WeatherGod commented on Jul 8, 2015
runsnakerun is awesome for visualizing cProfile output.
http://www.vrplumber.com/programming/runsnakerun/
On Wed, Jul 8, 2015 at 2:25 PM, Peter Würtz notifications@github.com
wrote:
jenshnielsen commented on Jul 8, 2015
Thats definitely bad. I still can't really reproduce it (OSX and Ubuntu 15.04 virtual machine). Does anyone have any idea what controls the event rate?
tacaswell commented on Jul 8, 2015
I wonder if the event selector rules are different and that the draws are being allowed to interrupt the event loop too often in Qt5
tacaswell commented on Jul 8, 2015
attn @mfitzp
pwuertz commented on Jul 8, 2015
It seems that (on systems I tested) Qt5 emits much more events leading to more FigureCanvasQt draw() calls. Ideally, a received event should notify Qt to do a deferred (costly) update the scene/figure. The current implementation however redraws the Agg canvas for every single event.
def draw(self): """ Draw the figure with Agg, and queue a request for a Qt draw. """ # The Agg draw is done here; delaying it until the paintEvent # causes problems with code that uses the result of the # draw() to update plot elements. FigureCanvasAgg.draw(self) self._priv_update()When I move the FigureCanvasAgg.draw() call to the paintEvent I'm getting excellent performance.
@mfitzp states that matplotlib requires a draw call at this time since other code might rely on the fact that draw() implicitly updates plot elements too, like tick label positions. When writing the PGF backend I experienced a similar dilemma and ended up implementing a dummy flag for ignoring any drawing calls to the renderer when calling draw() isn't actually used for drawing. Maybe it is possible to add something like this to FigureCanvasAgg as well.
The way draw() is designed/used in matplotlib leads to very cumbersome handling within the backends, especially when the actual drawing process is very expensive and spurious redraws must be avoided at all costs. Matplotlib should have a method like recalculate() instead of using draw() for updating plot elements.
pwuertz commented on Jul 9, 2015
So apparently the draw_idle and dynamic_update canvas methods are supposed to be called for notifying a GUI to repaint itself. For GUI event handlers I replaced all the synchronous draw calls with the asynchronous draw_idle methods (proposed fix: b87bd20). As far as I can see this completely fixes the problem for me. Any regressions on other platforms or comments concerning the modifications?
tacaswell commented on Jul 9, 2015
I am a bit concerned about losing the single-shot time logic in draw_idle,
but I may be misunderstanding how that is working.
On Thu, Jul 9, 2015 at 11:00 AM Peter Würtz notifications@github.com
wrote:
pwuertz commented on Jul 9, 2015
Yea, I must confess I don't understand why draw_idle was made that way. If I got the idea right, draw_idle should post a single redraw request to the event loop and prevent flooding the event loop with further requests as long as the outstanding draw wasn't dealt with. That is exactly what QWidget.update() is for, no need to reinvent it.
By the way, it would be interesting to check if this _priv_update() hack is still necessary on Qt5/Win. Did draw_idle emulate the intended behaviour of update() because it did (does?) not work on Windows?
tacaswell commented on Jul 10, 2015
I do not either and it is probably not worth the forensics to sort it out.
Can you make that branch into a PR?
mfitzp commented on Jul 10, 2015
I'm currently on holiday in Greece so won't be able to look at this til
next Wed.
For what it's worth I suspect it may be occurring due to Qt5 support for
high-resolution trackpads (and multi touch). This means Qt5 produces a lot
more events per scroll than Qt4.
attn @mfitzp https://github.com/mfitzp
—
Reply to this email directly or view it on GitHub
#4604 (comment)
.
Merge pull request #4612 from pwuertz/fix_4604