Skip to content

Slow/blocking panning in Qt5Agg backend #4604

Closed

Description

@pwuertz

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()

Activity

  1. WeatherGod commented on Jul 8, 2015

    @WeatherGod
    Member

    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:

    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.

  2. pwuertz commented on Jul 8, 2015

    @pwuertz
    ContributorAuthor

    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.

  3. jenshnielsen commented on Jul 8, 2015

    @jenshnielsen
    Member

    I cannot reproduce this with:

    PyQt 5.4.2 (Homebrew)
    Qt 5.4.2 (Homebrew)
    on OS X 10.10.3

  4. pwuertz commented on Jul 8, 2015

    @pwuertz
    ContributorAuthor

    I think I saw this on every Ubuntu system so far. Someone who can confirm this?

  5. WeatherGod commented on Jul 8, 2015

    @WeatherGod
    Member

    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:

    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)
    .

  6. jenshnielsen commented on Jul 8, 2015

    @jenshnielsen
    Member

    I will try to test it out on a Ubuntu system when I have a chance

  7. pwuertz commented on Jul 8, 2015

    @pwuertz
    ContributorAuthor

    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).

  8. jenshnielsen commented on Jul 8, 2015

    @jenshnielsen
    Member

    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 )

  9. pwuertz commented on Jul 8, 2015

    @pwuertz
    ContributorAuthor

    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.

  10. jenshnielsen commented on Jul 8, 2015

    @jenshnielsen
    Member

    As far as I can se it's calling figure draw 69 vs 242 times

    242    0.008    0.000    5.461    0.023 figure.py:1040(draw)
    69    0.003    0.000    1.686    0.024 figure.py:1040(draw)
    
  11. pwuertz commented on Jul 8, 2015

    @pwuertz
    ContributorAuthor

    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.

  12. added this to the milestone on Jul 8, 2015
  13. WeatherGod commented on Jul 8, 2015

    @WeatherGod
    Member

    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:

    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

    Different machine, different graphics driver, still Ubuntu.

    —
    Reply to this email directly or view it on GitHub
    #4604 (comment)
    .

  14. jenshnielsen commented on Jul 8, 2015

    @jenshnielsen
    Member

    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?

  15. tacaswell commented on Jul 8, 2015

    @tacaswell
    Member

    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

  16. tacaswell commented on Jul 8, 2015

    @tacaswell
    Member

    attn @mfitzp

  17. pwuertz commented on Jul 8, 2015

    @pwuertz
    ContributorAuthor

    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.

  18. pwuertz commented on Jul 9, 2015

    @pwuertz
    ContributorAuthor

    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?

  19. tacaswell commented on Jul 9, 2015

    @tacaswell
    Member

    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:

    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)
    .

  20. pwuertz commented on Jul 9, 2015

    @pwuertz
    ContributorAuthor

    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?

  21. tacaswell commented on Jul 10, 2015

    @tacaswell
    Member

    I do not either and it is probably not worth the forensics to sort it out.

    Can you make that branch into a PR?

  22. mfitzp commented on Jul 10, 2015

    @mfitzp
    Member

    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)
    .

  23. added a commit that references this issue on Jul 22, 2015
    bb9ec1b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions