Paint.NET and Performance — Thumbnails

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!

24 thoughts on “Paint.NET and Performance — Thumbnails

  1. Tim Yen says:

    That is great to hear rick.

    Have you thought of looking at the startup time when not opening an existing an image? Just opening a blank white canvas? Its pretty quick as is, but its not as quick as the old mspaint.exe. I noticed one previous post mentioned the check for updates will be moved out of the startup sequence in a new version. Is this going to make it as quick to load as the second time you load it up?

    regards,
    Tim

  2. Rick Brewster says:

    Tim — The update check is still done at startup. It’s the installation of that update which can now be deferred until you exit the program.

    But Paint.NET only checks for updates once every 5 days, so that’s the only time it’ll affect performance. And even then, it won’t be very much.

    I’m always on the lookout for ways to improve performance, especially at startup. It just has to be balanced with time spent on other things, and the safety of those optimizations. For instance, I wouldn’t dare do any “clever” optimizations a week before I’m getting ready to push out a final release!

  3. Dean says:

    Can I suggest you take out some of the memory from your P4 system? I would suggest 1GB is the *absolute maximum* you should have in your “minumum spec” machine. In fact, I’d go as low 512MB if you can (you might not have a 512MB DIMM lying around, though). Most casual users still have 1GB of memory or less in their Windows XP machines.

  4. Rick Brewster says:

    Dean – Right now I’m just trying to optimize the compute performance of Paint.NET, so having 2 GB in there is just fine. Right now “min spec” really means “min CPU spec.” When I decide that memory use needs to be profiled and optimized, I have some 256 MB sticks that I can make use of.

    Interestingly, the changes I’m making to the rendering systems in Paint.NET are actually *lowering* memory usage. So while the CPU requirements will be going up (a CPU with SSE1 support will now be required), there isn’t going to be a legitimate reason for me to increase the memory requirement of 256 MB.

    However, I may have to bump that up to 512 MB just because things like antivirus and Windows itself have a tendency to slowly creep up their memory usage as the years go by. No doubt SP3 uses up a little bit more than SP2, and antivirus engines are always adding more signatures, which take more and more memory as time goes on. Even then, I don’t have a hard block on the memory requirement, just on the OS, .NET version, and SSE.

  5. Crazy Man Dan says:

    Ooh. I like responsive. 🙂
    Being a single-core user myself (with a measly 1GB of RAM), performance enhancements are always good to hear.

    However, I do have one contention:
    (I still don’t know why no other imaging application does this! CS3 just dumps a list of files!)

    The GIMP too has thumbnails in the Open Recent menu for the last 10 images, as well as a Document History panel that has basically your entire image history (presently mine goes back to May 06) with thumbnails.

    So, it’s just the commercial applications that are lagging behind. Open Source FTW. 😉

  6. Aardvark says:

    I find it funny that “Galifray” is complaining about something taking too much **time**.

    Galifray, home of the Time Lords, creator of TARDISes? Anyone? (hmmm maybe that’s with 2 L’s?)

  7. Keithius says:

    So… how did HyperThreading affect the performance in this case? You said it made a “huge” difference, but you didn’t elaborate… just curious. 😉

  8. Rick Brewster says:

    Keithius — The reason that HT helps out a lot is because the thumbnail rendering thread is running at a low priority. If you only have 1 hardware thread, then the stuff I’m doing on threads with normal priority will cause the lower priority thread to not see much action. With 2 hardware threads, the low priority thread gets scheduled much more often and is actually able to make progress. Things are not faster from the standpoint of how much stuff gets calculated per second, but they are faster in terms of how quickly certain things react to the user.

  9. Lukasz says:

    I’ve been googling for this flaw many times and never get an answer why does the Paint.NET is so slow in opening litlle (several KBs) PNG files on my single-core system.

    I suspected that this behaviour was due to some “cache-routine”, that was scanning my system in order to get better performance and was waiting for response from a floopy drive or other device.

    Other than that Paint.NET 3 is a very good software, that helped me a lot when I needed to refine graphics for websites. Thanks! I’m looking forward for Paint.NET 4.

  10. Daniel Novak says:

    May I suggest using virtual machines to test Paint.NET on different “hardware”? Using virtualization you can easily drop the CPU to 1 and the memory as well. That’s how I work as a developer. =)

  11. Rick Brewster says:

    Daniel — I also use those. However, sometimes you need to see how a “real” system performs. Virtual machines are also subject to very different performance characteristics.

  12. Rick Brewster says:

    Daniel — Oh, and it’s worth noting that even in a Virtual PC it will run at the full single-thread performance of my Core 2 Quad 2.66ghz for compute-bound tasks. It’s often very edifying to see how things run with an honestly slower CPU. Hence the 2.26 ghz Pentium 4, which by now is about 6 years old. Plus the blit performance is very different with an XP VM in Vista, as opposed to a “real” graphics card in a “real” XP.

  13. Paint.NET Plugins says:

    Rick, you may be able to increase the perceived startup performance by moving the Plugin loading to a lower priority background thread. After all, how many people open Paint.NET and immediately open the effects menu? 😉

    ~BoltBait

  14. Rick Brewster says:

    BoltBait — Which would then introduce a priority-inversion performance problem, which is even worse 🙂 Paint.NET’s plugin system was never designed with the idea that people would install 100+ plugins. So the performance was never ever tuned for this scenario that’s becoming more common.

  15. Zwicky says:

    Does that mean better plugin handling for example 100 plugins could be a fix for PDN v4?

  16. kirby145 says:

    This is what was causing the slowness? That explains it though I wasn’t expecting it to just be thumbnails that’s kind of silly.

    Does ram affect Paint.NET’s performance? I have 510mb on XP and I’ll get more, if it’s going to help Paint.NET since that’s my only use on this computer. By the way, my Ram is used by my graphics card too.

  17. Whogopenguin says:

    Yes, I believe RAM will affect everything that runs. But it probably won’t change Paint.NET more than anything else.

  18. bernhold says:

    i have an athlon XP 2000 MHz machine and 512 mb of RAM, and it runs very slow. opening a number of images simultaneously takes about 2-3 minutes. but the program is rich of features 🙂

  19. Cristan Meijer says:

    I’m not sure if I’m entirely happy with this. I’m afraid that on my (fast) machine, the displaying of the temporally thumbnail and then replacing it with the final one would occur as a flicker (depending on its resolution).

    Still: from 28 seconds to 16 seconds boot time on an EEE-like pc (regarding speed) is an advantage you just can’t throw away!

Comments are closed.