You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Enabling %matplotlib qt in IPython shell slows it down significantly #14581
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)
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.
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?
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.
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.
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.
This inputhook is then used in a prompt_toolkit way I don't entirely understand to run the gui eventloop. I believe prompt_toolkit tries to run the inputhook whenever the console is idle basically. The Qt inputhook is here:
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.
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?
@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.
@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.
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.
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).
Activity
Carreau commentedon Nov 18, 2024
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 matploitlibAmerM137 commentedon Nov 19, 2024
I tried
%gui qt, exact same issue @Carreau, anything related to qt seems to slow down the console significantly.Carreau commentedon Nov 19, 2024
Maybe @ccordoba12 has an idea, (it also may affects spyder ?).
ccordoba12 commentedon Nov 21, 2024
According to the OP:
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?
AmerM137 commentedon Nov 21, 2024
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.
ccordoba12 commentedon Nov 21, 2024
Just monitor the IPython process memory in the Windows task manager to see if it increases constantly.
AmerM137 commentedon Nov 21, 2024
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.Carreau commentedon Nov 22, 2024
Oh, yeah, my bad. Reading too fast.
ccordoba12 commentedon Dec 20, 2024
Pinging @jdranczewski about this because they did several improvements to the Qt eventloop integration in IPykernel.
@jdranczewski, do you know what could be happening?
jdranczewski commentedon Dec 20, 2024
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):
%guimagic is implemented atipython/IPython/core/magics/basic.py
Line 518 in a265ad1
enable_guion the current shell.TerminalInteractiveShelland it implementsenable_guiby setting its_inputookattribute:ipython/IPython/core/magics/basic.py
Line 487 in a265ad1
prompt_toolkitway I don't entirely understand to run the gui eventloop. I believeprompt_toolkittries to run the inputhook whenever the console is idle basically. The Qt inputhook is here:ipython/IPython/terminal/pt_inputhooks/qt.py
Line 23 in a265ad1
inputhookimplementation: https://github.com/ipython/ipython/blob/a265ad1286acbb8d8c03146eb065defe5a42d312/docs/source/config/eventloops.rstMy 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:
ipython/IPython/terminal/pt_inputhooks/qt.py
Lines 60 to 62 in a265ad1
They were added in this pull request: #9706
IPykernel happily uses a
QSocketNotifierapproach 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?
AmerM137 commentedon Dec 20, 2024
@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.
AmerM137 commentedon Jan 2, 2025
@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.
jdranczewski commentedon Jan 4, 2025
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
QSocketNotifieron Windows is indeed that file descriptor watching doesn't work on Windows, and thatipykernelcan use this approach because it's watching a socket and not a file descriptor.Changing the 50ms to 10ms in
ipython/IPython/terminal/pt_inputhooks/qt.py
Line 67 in a265ad1
ipython/IPython/terminal/pt_inputhooks/tk.py
Line 84 in a265ad1
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
QEventLoopandQTimerevery cycle for example, which may be suboptimal if we end up doing it 5 times more often).Simple fix for ipython#14581
Improve Qt object management and performance (#14700)