Sunday, October 9, 2011

Comparison of Loading Techniques

In the last tutorial we started working with Qt's Designer - which can greatly improve your coding workflow if you use it correctly.  It can also become very confusing for other developers if you don't cultivate a well organized structure to the way you integrate designer files into your project.

This tutorial will cover some tips and tricks that I've found make working with designer files, taking advantage of the power that is provided with Designer while keeping the code clean and easy to follow.




Loading UI Files using the PyQt4.uic Module

There are generally two different ways for you to load a designer file into your widget.

  1. Use the PyQt4.uic.loadUiType function to generate a Ui_Form and a Widget class that for dual-inheritance for your Widget.
  2. Use the PyQt4.uic.loadUi function to load the ui data dynamically onto a widget within its constructor.
A simple example of the first syntax would look like:

#!/usr/bin/python ~/workspace/pyqt/benchmark/dual_inheritance_simple.py

import PyQt4.uic
from PyQt4 import QtCore, QtGui

MainWindowForm, MainWindowBase = PyQt4.uic.loadUiType('ui/mainwindow.ui')

class MainWindow(MainWindowBase, MainWindowForm):
    def __init__(self, parent = None):
        super(MainWindow, self).__init__(parent)
        
        # setup the ui
        self.setupUi(self)
        
if ( __name__ == '__main__' ):
    app = None
    if ( not app ):
        app = QtGui.QApplication([])

    window = MainWindow()
    window.show()
    
    if ( app ):
        app.exec_()

And a simple example of the second syntax would look like:

#!/usr/bin/python ~/workspace/pyqt/benchmark/loadui_simple.py

import PyQt4.uic
from PyQt4 import QtCore, QtGui

class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent = None):
        super(MainWindow, self).__init__(parent)
        
        # load the ui
        PyQt4.uic.loadUi('ui/mainwindow.ui', self)
        
if ( __name__ == '__main__' ):
    app = None
    if ( not app ):
        app = QtGui.QApplication([])
    
    window = MainWindow()
    window.show()
    
    if ( app ):
        app.exec_()

If you have worked through the previous designer example, you should already be a little familiar with the second syntax.  It is the simpler of the two as it doesn't really alter much from using UI files and using code to define the interface.

The first syntax follows more along the C++ style uic loading system that will generate middle template classes as it cannot dynamically generate the information as Python does.

Each syntax is an equally valid way to load a ui file - and each has pros and cons about them.

The first syntax is a bit more complex - it requires external classes to be created, and can create a bit of a disconnection between what the original Qt class is.  It also makes it slightly more difficult to have reusable loader logic (as we used in the previous tutorial) since you have to make sure to create unique class names for each widget you're defining.

The cons against the second syntax are that it is slightly slower (in milliseconds) as it has to parse and process the UI file each time the widget is created, and it is slightly more memory intensive (by a couple hundred kilobytes) if you continuously create widgets.

Generally, the speed and memory differences are so minimal, that I personally prefer the simpler syntax.  It is easier to read and simpler to setup.  Unless you are creating thousands of dialogs of the same class then you really won't notice a performance decrease.

Performance Tests

To test this, I setup a couple of benchmark testing to measure the speed and memory used for each syntax by creating a pretty complex widget to load.

The benchmark code is a little complex for this example, so I'm not go to post it - but if you are keen to try it yourself, I essentially just put a lot of print outs using the datetime.datetime.now() to clock start and end of loading/intializing windows, and used the psutil library to test different memory changes.

The important thing is that the code was 100% the same between the two tests except for the syle of loader that was used, and these were the results of creating a main window, and then 5 times loading a sub-dialog (similar setup as the last tutorial - only much more complex windows and sub-dialogs):

For the dual-inheritance syntax:

rk/dual_inheritance.py
-------------------
Creating window: 0:00:00.072103
Loading took: 0:00:00.267771
Loading used: 10908.0KB
-------------------
Creating dialog took: 0:00:00.007679
Creating dialog used: 0.0KB
Memory used since load: 3684.0KB
-------------------
Creating dialog took: 0:00:00.007316
Creating dialog used: 0.0KB
Memory used since load: -836.0KB
-------------------
Creating dialog took: 0:00:00.007407
Creating dialog used: 0.0KB
Memory used since load: -868.0KB
-------------------
Creating dialog took: 0:00:00.007085
Creating dialog used: 0.0KB
Memory used since load: -900.0KB
-------------------
Creating dialog took: 0:00:00.007015
Creating dialog used: 0.0KB
Memory used since load: -1044.0KB
-------------------

For the dynamic loading syntax:

rk/loadui.py
-------------------
Creating window: 0:00:00.098722
Loading took: 0:00:00.267666
Loading used: 10812.0KB
-------------------
Creating dialog took: 0:00:00.027258
Creating dialog used: 368.0KB
Memory used since load: -140.0KB
-------------------
Creating dialog took: 0:00:00.025032
Creating dialog used: 0.0KB
Memory used since load: 296.0KB
-------------------
Creating dialog took: 0:00:00.025029
Creating dialog used: 0.0KB
Memory used since load: 340.0KB
-------------------
Creating dialog took: 0:00:00.024843
Creating dialog used: 0.0KB
Memory used since load: 676.0KB
-------------------
Creating dialog took: 0:00:00.024829
Creating dialog used: 0.0KB
Memory used since load: 480.0KB
-------------------

As you can see, the speed difference is pretty negligable - however the memory usage can be a concern.  So, I'd recommend assessing your needs and seeing if you can get away with the simpler syntax (which you should be able to) and if you can't, switch to the dual-inheritance one.

3 comments:

  1. To do the equivalent of the "second syntax" (loading the widget UI from its constructor) using PySide, it's a bit more involving.

    See http://lists.pyside.org/pipermail/pyside/2010-December/001579.html for one way of getting it to work. Where you would call PyQt4.uic.loadUi, use the replacement loadUi function + helper class from that page.

    ReplyDelete
  2. I've been using the external uic programs (pyside-uic and pyuic I believe) to generate a module and from that module import the widget class and QtGui/QtCore. It does require you to create a .py version of your .ui file but that can be automated. The big advantage is that you can code without having to worry about whether you're using PyQt or PySide! It's a big deal for me as I'm using lots of different systems to play with this stuff.

    Keep it comming, I'm learning a ton!

    ReplyDelete
  3. If I use the second method then I don't have the ui objects in autocomplete. Is there a way to fix that?

    ReplyDelete