PDA

View Full Version : Keeping your application responsive



chezifresh
29th March 2011, 19:31
A little background... I've built a thumbnail view similar to what is outlined in this post (http://www.qtcentre.org/threads/33952-Thumbnail-View). My previous version of a thumbnail view involved a QFlowLayout, a QScrollArea and a custom thumbnail widget. I also created a thumbnailing service, which is essentially a class with several thumbnail loading threads. Ask the service for a thumbnail, it hands off the job to a thread, emits a signal returning a QImage to the service, which emits a signal with a QPixmap to the requester.

To make it faster and more responsive I've done the following:


Use a QListWidget instead of custom widgets. This is way faster and more responsive. It uses the delayed layout feature of the QListWidget so I can easily resize the icons and maintain some responsiveness
The thumbnail service batches up results from the thumbnail thread and only emits a signal after a timer has gone off, so as to not emit signals too often if the thumbnail loading threads finish in quick succession.


The slow part seems to be when the thumbnail service gets QImages back and it converts them to QPixmaps but its a little hard to tell since this program is difficult to profile.

Anyway, I wanted to share my technique for creating a responsive application and see if anyone has other ideas or techniques to keep the ui interactive while processing.

javimoya
29th March 2011, 19:45
qtconcurrent is another approach you should try

wysota
29th March 2011, 21:18
Keeping the GUI Responsive

javimoya
29th March 2011, 21:40
wysota... do you know an EASY book or website to start learning about threads and pararell programming (basic concepts)? it's focused on Qt it would be nice.
thanks

chezifresh
29th March 2011, 22:07
Good link. Aside from some details, that describes basically what my thumbnailing service does.

The problem I ran into was that my thumbnailing service sometimes produces thumbnails so fast that the UI thread is swamped with repaint events, and causes the UI to become unresponsive.

The way I approached this was to have my thumbnail scaling/loading threads emit a signal with the QImage it generated. The thumbnail service adds that thumbnail to a queue of thumbnails that need to be dispatched. Then it waits for a timer to go off. The timeout() signal is connected to a slot that dispatches the thumbnails:

Pseudo code...


void ThumbnailService::thumbnailLoadedSlot(QString path, QImage image) {
thumbnailQueue.insert(path, image);
timer.start();
}

void ThumbnailService::processImageQueue() {
timer.stop();
QMapIterator<QString, QImage> it(thumbnailQueue);
while (it.hasNext()) {
it.next();
QString path = it.key();
QImage image = it.value();

// converting to a QPixmap is a slow operation
QPixmap result = QPixmapCache.insert(path, QPixmap::fromImage(image));
emit(SIGNAL(finished(QString, QPixmap)), path, image);
}
if (!thumbnailQueue.isEmpty())
timer.start();
}

The good thing is, if it takes a long time to thumbnail a directory of images, its actually a very responsive widget. The only time I have problems is when the thumbnails come back too quickly.

wysota
29th March 2011, 23:11
wysota... do you know an EASY book or website to start learning about threads and pararell programming (basic concepts)? it's focused on Qt it would be nice.
Well... Silbershatz's book on operating systems is a classic. Besides that I don't know any such resources. In my opinion my Qt Quarterly article filled some of the gaps regarding Qt perfectly when it comes to practical approach to SIMD. If you need something specific then say so, I can try to do some research and write more about it. But basically it takes a lot of thinking and practice to understand threading and parallel programming on a decent level. You can look for articles on "grid computing" (or cloud computing), that's a popular subject nowadays and there are a lot of conferences dedicated to it, like this one: http://www.ppam.pl/ to just name the one I participated in (and it helped start my interest in parallel programming). The basic thing you should undestand is how processes in your OS work, then you can learn about threads and synchronization and then you can focus on more high-level aspects like parallel programming, SIMD and stuff like that. OpenCL or equivalent should then follow if you want a many-core (note, not "multi-core") solutions.

Oh, and good computer science studies also help a lot :)


Good link. Aside from some details, that describes basically what my thumbnailing service does.
The main difference is that the solution described in the article adjusts to your machine and is more high-level.


The problem I ran into was that my thumbnailing service sometimes produces thumbnails so fast that the UI thread is swamped with repaint events, and causes the UI to become unresponsive.
There is an easy solution - process the data you receive in batches. The simplest way is to use a timer, like so:


void MyModel::newImageReady(const QImage &img) {
if(!m_batchTimer.isActive()) m_batchTimer.start(1000); // timer is single shot
m_pending << img;
if(m_pending.size()>=10) {
// force update when 10 images are waiting
m_batchTimer.stop();
batchTimerTimeout();
}
}

void MyModel::batchTimerTimeout() {
int minRow = rowCount(), maxRow = -1;
foreach(const QImage &img, m_pending) {
QModelIndex idx = indexFromText(img.text("index"));
minRow = qMin(minRow, idx.row());
maxRow = qMax(maxRow, idx.row());
QPixmap px = QPixmap::fromImage(img);
m_data[idx.row()].pixmap = px;
}
m_pending.clear();
emit dataChanged(index(minRow, 0), index(maxRow, columnCount()-1));
}


The way I approached this was to have my thumbnail scaling/loading threads emit a signal with the QImage it generated. The thumbnail service adds that thumbnail to a queue of thumbnails that need to be dispatched. Then it waits for a timer to go off. The timeout() signal is connected to a slot that dispatches the thumbnails:

I should really read posts to the end before answering them :) Yeah, good approach but mine is better - queue in the model, not in the service, worker threads can't create pixmaps.

chezifresh
30th March 2011, 01:45
The main difference is that the solution described in the article adjusts to your machine and is more high-level.


I'm doing some adjustments based on the machine. The problem is, I'm using PyQt, which is missing a lot of QtConcurrent APIs. All I have to work with are QThread and QThreadPool. So I just create QtCore.QThreadPool.maxThreadCount() - 1 threads (save one core for the ui thread) to do all of the work. I'm pretty sure it would be better if I could use QRunnable and QFutureWatcher though. They probably have a good reason for not implementing them, like the fact that Python cannot be truely multi-threaded do to the GIL, but QThread seems to get me the performance I need in terms of background, high I/O latency operations.




... queue in the model, not in the service


I actually started off doing something very similar. I think my problem is that I have two at least two widgets that display the thumbnails and I didn't implement my own model. In other words I have a QListWidget and a QTreeWidget for a thumbnail view and detail list view respectively, as opposed to a QListView and QTreeView using the same model. That may turn out to be a mistake, but I think what I have might at least turn out to be an incremental improvement. A shared model makes sense in all sorts of ways, but thats a really good suggestion



worker threads can't create pixmaps


Definitely. The worker threads I currently emit a signal with the path and a QImage, which goes directly to the thumbnailing service, or thumbnail manager which runs in the main ui thread. In the main ui thread it then converts the QImage to a QPixmap and broadcasts that the QPixmap is available.

I'll try what you've suggested in terms of batches, I was doing it time based, so that the foreach broke out if it took more than 0.1 secs to process the items in the queue. What your said might lead to more consistent results.