One thing I’ve always had fun with in client development is performance. Paint.NET is quite heavily optimized for a short startup time, as well as for multicore for various rendering kernels (and for the effect system in general). So when I see a comment like this over on BetaNews …
Reviewer: Galifray
It’s a good, strong program, but it has some flaws that have not been fixed. On slower systems, like mine, it can take ten or more seconds to open images, regardless of the image’s size. The program simply hangs with a busy cursor until it’s finally ready. This is a real annoyance when I’m attempting to open several images at once, or I’ve pasted content into a new image.
… it saddens me a little, and I immediately want to fix it 🙂 I personally hate it whenever a program has a busy cursor for no reason that I can discern. In fact, I know exactly what’s causing this. When Paint.NET loads an image, it immediately generates two thumbnails. The first goes into the File->Open Recent menu (I still don’t know why no other imaging application does this! CS3 just dumps a list of files! update: apparently GIMP does this!). The second goes into the image thumbnail list in the the top-right of the window, and is something that is continually updated as you work on or change the image.
The problem is that Paint.NET is waiting for both of these thumbnails to be created before letting go of the “busy” cursor and allowing you to actually do anything. It isn’t something you’ll really notice on a dual- or quad-core system, and since I haven’t had a single-core system in 5 years I’ve never put much thought into it. Plus, the desktop market is increasingly not single-core (even $50 Celerons are dual-core now), so part of me has dismissed it as a problem akin to dial-up versus broad-band: over time, it just fixes itself.
I decided to fix this anyway, as it would make the application faster and more responsive for all systems, as well allow me to brush up on some asynchronous programming in a relatively safe area of the code. In order to fix the problem, I first needed to recreate and empathize with the situation. My development box has quad-cores* and 8 GB, which basically means I’m on a totally different planet than most users. I scrounged up some old computer parts and, voilà , I now have a good low-end bread-boarded system for performance testing:
It’s a Pentium 4 at 2.26 GHz with 2 GB of DDR400 memory running in single channel DDR266 mode. The board is an Intel 865 “PERL” and supports dual channel DDR400, but I specifically wanted the lower performance of single channel mode. I also have a 2.8 GHz Pentium 4 chip with HyperThreading so I can test how that extra hardware thread affects things. (For this scenario, it actually makes a huge difference!). I installed Windows XP SP2, and then Paint.NET v3.36, and opened up a bunch of images: sure enough, the performance sucked. I now have the empathy I’d been seeking.
Anyway, back to the problem at hand. There are at least two more places where thumbnail synchronization happens in Paint.NET v3.xx. The first is when you open the “image list menu” (press Ctrl+Q): it will wait until all thumbnails are up to date before showing you the menu.
The second is when you close a single image that has unsaved changes. If the thumbnail isn’t up to date, this dialog will not show until it is.
The wait to bring these up can be significant in certain pathological scenarios, involving very large images, slower systems, or priority inversion (the thumbnail generator thread runs at low priority). Contrast to the multiple image version of the Unsaved Changes dialog, which doesn’t wait for thumbnails and adds them to the dialog as they finish rendering. The dialog comes up immediately even if you have 100 images open that all have unsaved changes.
I want and need Paint.NET v4 to be responsive at all times. The response time of the application should not be [linearly] proportional to the size of the data being manipulated, or to the latency of retrieving/computing that data. The example I always give to help explain this is Google Maps / Live Search Maps. When you scroll around, it doesn’t jitter around while it downloads any specific tile. Instead, it gives a generic tile that effectively signals to the user that it is still being downloaded. Client applications should also strive to this level of responsiveness. To accomplish this requires a lot of asynchronous programming, and this was a good place to get started.
So, in the current Paint.NET v4 codebase, I’ve made the following changes:
1) For the Open Recent thumbnail, it is offloaded to a background thread. Since it’s possible for you to open up this menu before the thumbnail is done, it currently just has a “blank” thumbnail if you’re that quick. This has created another race condition that I plan to deal with later. For example, if you’re quick you can scribble on the image you just loaded and that scribbling will then show up in the Open Recent menu. Woops 🙂 This will be fixable once I make changes to the read/write model employed by the data layer.
2) For the image thumbnail list in the top right, I’m removing all code that “synchronizes” on it. There is a little “busy” animation for when the thumbnail is being generated for the first time (thank you Tango).
3) For the image list menu (Ctrl+Q), if a thumbnail isn’t available yet then it will just show a blank thumbnail.
4) For the unsaved changes dialog, it will use the same trick as the image thumbnail list: a “busy” animation while the thumbnail is being generated. It will never block on this, so if you are faster than the thumbnail rendering then you will save a few precious seconds. This required adding support to my Task Dialog so that it could support an animation and not just a static image.
The Layers window does not yet have the animation for a thumbnail that isn’t ready yet. I’m planning to do significant work in that area later, and so I’ve saved this work for then. And I definitely need to write some classes so that I can support animations in the UI a lot better. Right now each occurrence is manually setting up timers, etc.
The result? In Paint.NET v3.36 on that 2.26 GHz Pentium 4, it takes 28 seconds to load a batch of eight 7 megapixel images. There are also periods of time where it appears to “stall” for no good reason. Paint.NET v4 does it in 16 seconds, and has none of the obnoxious stalls. You just end up with several thumbnails in the top-right window that show up as “busy” animations until the thumbnails have finished rendering, during which time you’re free to do whatever you want. As a bonus, performance is also noticeably faster on my quad-core development box.
My ad-hoc Pentium 4 box is proving to be quite useful for performance testing. Over the last few days, Paint.NET v4 has significantly improved in performance. I’ve so far rewritten the front-end of the rendering engine, and had to come up with some interesting tricks to keep the performance good. The first revisions of this code were plenty functional, but very naive from a performance standpoint.
* Until Nehalem comes out, that is. Then it’s time for a dual Xeon box. Sixteen threads!