Search code examples
matplotlibpyqtplatformqtimer

PyQt6 app with QTimer is much slower on Windows then the same machine's WSL


I'm developing a PyQt6 app that plots data from a measurement instrument and something else related to user input, and I'm experiencing outstanding lags when I run it on Windows vs on the WSL (Windows Subsystem for Linux) of the same machine! Here are a few details about the Qt objects I'm using there:

  • I plot the data using matplotlib.backends.backend_qtagg.FigureCanvas.
  • I use QtCore.QTimer with the smallest possible interval of 1ms (according to docs) to get data from the measurement instrument and update the plot with it.
  • I'm plotting measurement data from multiple channels of the instrument, and currently a single QTimer is used to update all of them.

Could it be the QTimer usage doesn't fit this usecase? Perhaps I should use a QThread? Or a QThread per channel? I was also thinking about switching to pyqtgraph....

Which ever change from the above I'll apply to my code, it will improve the performance of it on all platforms, so in either way, it would be very interesting to get a clue why running it natively on Windows is so much slower.


Solution

  • Adding a print statement in the timer's timeout function, would show you that the GUI is not exactly stuck, since the timeout function keeps on printing even when the GUI seems to be stuck. This is a sign that the Qt event loop doesn't manage to handle the events between the timer's timeout function calls.

    On different platforms, the event loop is implemented differently, and also CPU / system load may impact, so in such low-interval timers, it is safe to process events manually. Here's a minimal working example:

    import time
    
    from PyQt6 import (
        QtWidgets,
        QtCore,
    )
    
    class ApplicationWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self._main = QtWidgets.QLabel()
            self.setCentralWidget(self._main)
            self._timer = QtCore.QTimer()
            self._timer.timeout.connect(self.updateTimerLabel)
            self._timer.start()
    
        def updateTimerLabel(self):
            self._main.setText("Time now is {}".format(time.time()))
            # This is the important part!
            qapp.processEvents()
    
    
    qapp = QtWidgets.QApplication([])
    app = ApplicationWindow()
    app.show()
    app.activateWindow()
    app.raise_()
    qapp.exec()
    

    See also: