Profiling A PyQt4 Application

Posted on June 11th, 2009 by raoul and tagged , , , , , , , , , .

From time to time, I've talked about my open source lyrics projection application, openlp.org, which is written in Python and Qt4 using the PyQt4 bridge. One of the core features of openlp.org is to display lyrics on a second monitor using the current "theme," which is a collection of settings for displaying the lyrics (background, font, etc).

Recently, we've hit a bit of a snag to do with the lyrics display. Functions in the Renderer class, which performs the drawing, were taking incredibly long to execute, which meant that the application was almost hanging whenever you wanted to load a song (be it into the preview or live slide controllers). So we started doing some investigation.

Tim Bentley managed to figure out that the QPixmap.scaled() function was the main problem. When creating the small preview image, we resize the background image (using the scaled function) and then paint the words on there. Martin Thompson then e-mailed the developer mailing list with some really handy info - a Python profiler.

First, if you're on an Ubuntu or Debian based like me, you need to install the python-profiler package. Once that is done, run your application like so:

$ python -m cProfile -o openlp.prof openlp.pyw

Where "openlp.prof" is the profiler's output file, and "openlp.pyw" is your application's main file. 

Then create a small script (I called mine "profiler.py" ) with the following code in it:

import pstats
p = pstats.Stats('openlp.prof')
p.sort_stats('total').print_stats(20)

Run this file after you have run your application, and it'll read the profiler file and create a summary with the top 20 results, ordered by the internal time each function took.

This helped a lot. I was able to time my functions as I played around with various Qt4 classes. 

While reading up on various rendering techniques and Qt4 classes, I came across the following sentence on the QImage page:

QImage is designed and optimized for I/O, and for direct pixel access and manipulation, while QPixmap is designed and optimized for showing images on screen.

When I replaced the QPixmap that we used to scale the background image down with to a QImage, the scaled() function dropped from 3 seconds to 0.635 seconds. A marked improvement!

However, the QPixmap.fromImage() function was taking a while, so I replaced ALL the QPixmaps with QImages, and suddenly my total rendering time went down to less than 1 second, instead of 3 - 4.

In the mean time, Tim Bentley had another look at the rendering functions himself, and through reducing the number of calls to a few functions, managed to significantly reduce the rendering time even further!

For both of us, the Python profiler was invaluable.

Comments

Re: Profiling A PyQt4 Application

wonderful stuff. Many thanks, I have used this to profile my pyqt4 application. on my system, however, the line p.sort_stats('total').print_stats(20) threw a Keyerror. p.sort_stats('ti').print_stats(20) worked for me though. (I'm running python2.6, dunno if that's the issue here) Thanks again Raoul. Neil.
Comment by rowinggolfer on Sep 24th, 2009 at 8:41 am