Alex headshot

AlBlue’s Blog

Macs, Modularity and More

Optimising GUI performance

2004, java

It seems that there are many types of GUI application that need to have bleeding performance in a mission-critical system. These are ones that are responding to many different types of updates, and have to update the notification mechanism whenever a change comes in.

The problem is that Java's GUI (whether Swing or AWT) wasn't really designed for having heavy loads thrust upon it, to the extent that there is a major bottleneck in the form of the AWT Event Queue. In fact, there's a discussion about the issues surrounding AWT threading, and in particular one of my vociferous bugs in 4030718 regarding the retarded behaviour that 1.2 and 1.3 had forcing System.exit() to be used with any GUI apps. (Nice to see that they've implemented a fix in 1.4, though not using my more elegant implementation suggested in the bug report).

Back to the story in hand. They removed synchronized when moving from AWT to Swing (because Swing was -- and still is -- hideously slow) as a mechanism to speed up redraws, but then they needed to ensure that threading race conditions didn't kill the UI performance. So they created SwingUtilities to fix these problems.

What SwingUtilities does is to enque a request (in the form of a Runnable object, since Java doesn't have anything nice like code blocks) into the single 'EventQueue' thread. Thus, problem solved, and all access is single threaded. They go to great lengths to explain this in an article on Swing threading along with a pretty lame excuse for implementing it this way towards the end of the article.

So, basically, if you're using a multi-threaded system, you're safe if you use this queue.

Of course, if you want to limit yourself to a few tens of updates per second, you can use this approach. But for heavy systems, where timely updates of data is critical, then pretty much ignore everything you've read about Swing threading before.

Optimisation tricks:

  1. Firstly, don't bother with queueing requests. Every time you do an update on a component, it's going to send through a repaint() anyway, which goes into the single thread queue for a re-draw. So you'll still have single-threaded code, but you've outsourced the changing implementation to a less heavily used thread. You may argue (and some do) that this causes problems; but let's face it, people -- the only thing you're going to notice is a sub-millisecond display glitch between when the data is set and when the data is redrawn anyway, so it will happen so fast the user isn't going to notice it. About the only times you need to queue requests (and even then, it's debatable) is when you're building a UI or in some cases, changing size of the UI. If you're merely updating the data displayed in a component which will be the same size before and after, then single threading isn't necessary.

  2. Secondly, don't bother with repaint(). All it does is post an event onto the queue, which forces a redraw immediately (when the event is processed). Instead, you should use repaint(100) or some such small number; that says, redraw this component sometime in the next 100ms. Several repaints will cause events to be posted to the queue, but only one will happen in that timeframe for that component. This in itself can help with the redraws of a particular region of code, if you know which ones have never been changed.

  3. Lastly, don't flood the system event queue -- it can only process things so fast. As an example, I set up a routine to display a button in a JFrame, and change the label repeatedly. Using a direct loop, I achieved 10000 changes in 0.4s using a direct call, and 5.1s using a SwingUtilities.invokeLater() call. It took 6.2s with SwingUtilities.invokeAndWait(). Of course, some of this inefficiency will be due to creation of a new object anyway; but this is exactly what happens in the case of posting an event, so it's a fair comparison. Incidentally, I re-ran the test using 'new Object()' and ran without the JIT enabled, and got the same 0.4s result; so it's not the creation of a new instance that's the big issue anyway.

It's a shame that multi-threaded access is one of the less well understood parts of Java, since it's got so many good things going for it, and even less so when Sun even document the 'safe rather than sorry' approach. In many cases, outside of construction, a UI doesn't have that much to go wrong -- and if it does, a simple UI call to 'refresh the whole window' is more than sufficient to get it to update the display. But if you are finding your UI can only handle so many responses, then this might be a good starting point to optimise code.