Skip to content

Enabling %matplotlib qt in IPython shell slows it down significantly #14581

Closed

Description

@AmerM137

To reproduce:
Fire up a terminal, activate conda environment and enter ipython. This is a normal ipython terminal (not jupyter console or jupyter qtconsole).

At first, the shell is responsive even after many imports and trying out different data manipulation operations. As soon as %matplotlib qtis enabled, the shell slows down to a crawl, both input and time to show output. It feels as if there is a large roundtrip that is being taken by the input.

I understand that the matplotlib event loop needs to hook into ipython's loop or something along those lines, but it's very slow and the input delay is significant.

Other backends/frontends:
It looks like the issue does not exist with jupyter-qtconsole.
Update 11/17/24: Performance degradation does not happen with c.InteractiveShellApp.matplotlib = "tkagg"
Looks like something is causing the qt backend to degrade performance.

OS: Windows 11 24H2 Build 26100.2314
Terminal: Windows Terminal 1.21.2911.0 (native, no WSL)

Relevant packages:
python            |     3.12.7   |       hce54a09_0_cpython |  conda-forge
ipython           | 8.29.0   | pyh7428d3b_0 | conda-forge
ipython_genutils | 0.2.0  |  pyhd8ed1ab_1 |  conda-forge
prompt-toolkit |   3.0.48 |    pyha770c72_0 |        conda-forge noarch
pygments        |  2.18.0 |    pyhd8ed1ab_0   |      conda-forge noarch
matplotlib        | 3.9.2   | py312h2e8e312_2 | conda-forge
matplotlib-base  |  3.9.2   | py312h90004f6_2 |  conda-forge
matplotlib-inline | 0.1.7    | pyhd8ed1ab_0   |  conda-forge
jupyter                  | 1.1.1   | pyhd8ed1ab_0 | conda-forge
jupyter-lsp              | 2.2.5  |  pyhd8ed1ab_0 | conda-forge
jupyter_client           | 7.4.9  |  pyhd8ed1ab_0 | conda-forge
jupyter_console       |    6.6.3 |   pyhd8ed1ab_0 |  conda-forge
jupyter_core            |  5.7.2   | pyh5737063_1 |  conda-forge
jupyter_events        |    0.10.0 |  pyhd8ed1ab_0  | conda-forge
jupyter_server        |    2.14.2  | pyhd8ed1ab_0 | conda-forge
jupyter_server_terminals  0.5.3  |  pyhd8ed1ab_0 | conda-forge
jupyterlab              |  4.3.0 | pyhd8ed1ab_0 | conda-forge
jupyterlab_pygments  |     0.3.0  |  pyhd8ed1ab_1 | conda-forge
jupyterlab_server       |  2.27.3  | pyhd8ed1ab_0 | conda-forge
jupyterlab_widgets    |    3.0.13  | pyhd8ed1ab_0 | conda-forge

I am happy to run any tests on my end to narrow down the issue. I have a fairly beefy workstation and this is really the only situation where I encounter lag with my inputs/outputs in the terminal.

Activity

  1. Carreau commented on Nov 18, 2024

    @Carreau
    Member

    Hum, I'm not sure how to debug that, especially on windows.

    Try %gui qt, instead of %matplotlib qt, I expect the same issue; but at least we'll be sure it's not due to matploitlib

  2. AmerM137 commented on Nov 19, 2024

    @AmerM137
    Author

    I tried %gui qt, exact same issue @Carreau, anything related to qt seems to slow down the console significantly.

  3. Carreau commented on Nov 19, 2024

    @Carreau
    Member

    Maybe @ccordoba12 has an idea, (it also may affects spyder ?).

  4. ccordoba12 commented on Nov 21, 2024

    @ccordoba12
    Member

    According to the OP:

    It looks like the issue does not exist with jupyter-qtconsole.

    So, it shouldn't affect Spyder (we haven't heard about slowdowns in our issue tracker either).

    But maybe this is a due to a memory leak in the prompt-toolkit implementation of the Qt event loop. @AmerM137, could you check if that's the case for you?

  5. AmerM137 commented on Nov 21, 2024

    @AmerM137
    Author

    I need some instructions, please. How do I check for memory leaks?

    And yes, this seems to only affect the ipython terminal.

    As soon as the qt plotting backend is invoked, a delay is introduced. It really feels like any input in the terminal is making a long roundtrip to the shell, or something like that.

  6. ccordoba12 commented on Nov 21, 2024

    @ccordoba12
    Member

    I need some instructions, please. How do I check for memory leaks?

    Just monitor the IPython process memory in the Windows task manager to see if it increases constantly.

  7. AmerM137 commented on Nov 21, 2024

    @AmerM137
    Author

    I've monitored the process for about 15 minutes. I don't see any memory leaks. IPython seems to be using 0.4 MB, the python process using ~88 MB. I don't see memory usage going up at all.

    @ccordoba12 I initially noticed this problem with very short-lived IPython REPL sessions. As an example, I usually open a terminal in a folder that contains a csv file, import pandas/matplotlib, load the data and plot a few charts to see what the data looks like, then exit the repl. With that said, I doubt any memory leak is causing the issue I am seeing.

    A very simple way to see what's happening is when a REPL is started with ipython --matplotilib=qtagg, type a long line of characters, then hold backspace to delete character by character. The deleting is very choppy. Running IPython with no backend or the tkagg backend does not suffer from the lag/choppiness.

  8. Carreau commented on Nov 22, 2024

    @Carreau
    Member

    It looks like the issue does not exist with jupyter-qtconsole.

    So, it shouldn't affect Spyder (we haven't heard about slowdowns in our issue tracker either).

    Oh, yeah, my bad. Reading too fast.

  9. ccordoba12 commented on Dec 20, 2024

    @ccordoba12
    Member

    Pinging @jdranczewski about this because they did several improvements to the Qt eventloop integration in IPykernel.

    @jdranczewski, do you know what could be happening?

  10. jdranczewski commented on Dec 20, 2024

    @jdranczewski
    Contributor

    Hey! The IPython console enters eventloops in a fairly different way from IPykernel, but I've done a small dive into how the IPython way works, and could have a direction to look in? Flying home for the holidays tomorrow, so it may be a little while before I can look into this more than the stuff below.

    For reference (and to organise my thoughts):

    My testing on Windows shows that pressing down a key to input a repeating character populates the terminal just a little bit slower when the qt loop is enabled.

    I think the problem is the separate implementation of the loop on Windows, which uses a 50ms timer to exit instead of hooking into a file descriptor notifier like on Linux. Qt timers are only guaranteed to last at least roughly the specified time, so in reality I'm seeing inputhook times of 55-200ms. This could result in perceptible lag, as I'm definitely seeing the hook being called while the user types in the console.

    The reason for the timers is explained here:

    if sys.platform == 'win32':
    # The QSocketNotifier method doesn't appear to work on Windows.
    # Use polling instead.

    They were added in this pull request: #9706

    IPykernel happily uses a QSocketNotifier approach to exit the Qt loop on Windows:
    https://github.com/ipython/ipykernel/blob/main/ipykernel/eventloops.py#L99-L106

    But a quick test on my Windows machine seems to not work with the notifier approach (which should in theory be much smoother).

    @AmerM137 would you be able to test if the issue occurs in a Linux/unix system, perhaps Windows Subsystem for Linux?

    Next steps for me would be figuring out why the notifier thing works in IPykernel but not IPython - perhaps its a socket vs file descriptor thing? It could be that this is a very different issue altogether though - @AmerM137 is 50-200ms lag per character consistent with what you see, or is it more?

  11. AmerM137 commented on Dec 20, 2024

    @AmerM137
    Author

    @jdranczewski 50-200ms lag is definitely consistent with what I am seeing. I don't think it's more than that, but I imagine it will be worse on slower machines. It's very jarring when typing, and it feels like the latency is there on every key stroke, that's why I notice when I hold backspace to delete characters, for example.

    I'll test it on WSL soon and let you know. I have a laptop that is running Pop_OS as well, I'll test it there.

  12. AmerM137 commented on Jan 2, 2025

    @AmerM137
    Author

    @jdranczewski I test this on my Pop_OS machine and I don't see the same lag I experience on windows. If there is any lag, it's not really noticeable. This issue seems to be specific to windows. Hope this helps.

  13. jdranczewski commented on Jan 4, 2025

    @jdranczewski
    Contributor

    Hi @AmerM137, thanks for checking on Linux! That makes sense, and seems to confirm the issue is with the inputhook hogging the process with a timer rather than some performance problem I'm not seeing.

    On my side I've confirmed that the reason we can't use a QSocketNotifier on Windows is indeed that file descriptor watching doesn't work on Windows, and that ipykernel can use this approach because it's watching a socket and not a file descriptor.

    Changing the 50ms to 10ms in

    timer.start(50) # 50 ms
    does remove the apparent lag on my end, and brings the Qt implementation in line with the Tk one

    I think this would be a simple solution to this problem, but I would like to do a bit more profiling and figure out if we can do this a little more efficiently (the current implementation creates a new QEventLoop and QTimer every cycle for example, which may be suboptimal if we end up doing it 5 times more often).

  14. added a commit that references this issue on Jan 30, 2025
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions