What's coming in Paint.NET v3.5.5: Performance!

There won’t be any new features this time around, of course, since it’s just a +0.0.1 release. There’s only 1 bug that I’ve found which I’ve decided to fix, which has to do with saving “large” 8-bit PNG/GIF/BMP images and is detailed over on the forum (summary: I was using 32-bit integer accumulators and they were overflowing; switching to 64-bit accumulators fixed it). Any plugin that makes use of the built-in quantization code will also be fixed.

So I’ve decided to add some more value to this release in the way of performance optimizations. The first optimization affects an admittedly small population of users: those who have Intel Atom-based nettops. The Intel Atom D510 and 330 chips are dual-core CPUs with HyperThreading and so they show up in Task Manager as 4 CPUs. Normally Paint.NET always uses multisampling to render the canvas. However, if the system has less than 4 CPUs it will use nearest neighbor while doing a zoom in/out with the mouse wheel. Since the Atom reports 4 cores, it was not benefitting from this, but it is now noticably zippier.

The second optimization is for everyone. One thing I’ve been wanting to do for awhile is to move some of the rendering kernels into C/C++ land. The reason for this is that the Visual C++ compiler can do a lot better static optimization than either the .NET JITter or NGEN.

With just a few hours of work today, I’ve managed to make Gaussian Blur about 42% faster. I’ve also applied this trick to the Normal, Multiply, and Overlay blend modes and am getting 15-20% faster performance. The “code” is almost exactly the same except with “uint” swapped for “unsigned __int32”, some marshalling logic, P/Invoke glue, etc. The bang-to-buck ratio here is great.

And of course, your mileage may vary (YMMV). These benchmarks were done on a quad-core Intel Core i7 at 4GHz running 64-bit Windows 7. I also ran them on my dual-core Atom 330, and the improvement was 35% for Gaussian Blur, and 30+% for the blend modes.

With v3.5.5 I will probably limit these optimizations to a few select areas in order to “test the waters.” If stability is good, as determined by the stream of crash logs I get (or don’t!), then I’ll bravely expand to other areas. I expect to release v3.5.5 by the middle of April.

Oh, yeah. On the Paint.NET v4.0 front, the decision has been made: it will require Win7/Vista minimum. I’ve already got most of the interop layer for Direct2D, DirectWrite, and Windows Imaging Component (WIC) written. The first feature to use these was a replacement of “my” super sampling code for WIC’s “Fant” resampling. This means that the quality of Image->Resize when shrinking an image has been greatly improved. I also have a version of the Curves adjustment that uses Direct2D instead of GDI+ for rendering its UI (this was mostly “prove to myself it actually works” code).

Please do remember that Paint.NET v4.0 probably won’t be available until late 2011 – you have plenty of time to enjoy XP with Paint.NET v3.5.x in the meantime if that’s the way you roll (and it’s not like I can delete it off your box once 4.0 ships, nor would I if I could). As a pre-emptive snark, in true Raymond Chen style: this was a decision I made after much deliberation with both quantitative and qualitative data, and spamming my comment box won’t change my mind. Your voice has already been heard. Thanks in advance.

27 thoughts on “What's coming in Paint.NET v3.5.5: Performance!

  1. Rick Brewster says:

    I just ran the benchmarks in 32-bit, and they are not as exciting: only about 10%. Still, it’s worth shipping.

    Yet another reason to upgrade to 64-bit!

  2. James says:

    Always AWESOME geeky info.

    There’s just one thing you keep mentioning that I’d love for you to write about: your crash reports.

    Not having worked with them, but having sent a TON of them, I’d love to see how they’re handled on your side. For example:

    – How do you receive them?
    – How do you read them?
    – How do you use them to improve/fix a feature?

    etc.

    Anyway, great app, great info.

  3. Tao Yue says:

    What this seems to say is that .NET generates pretty decent native code on x86, but that native codegen has not yet been optimized for amd64.

    I wonder if the managed code versions of the filters perform any better on .NET 4.0.

  4. Hans Wurst says:

    If you move into the unmanaged realms you will have to maintain two versions of some code, specialised for 32- and 64-bit?

  5. Bartek says:

    Just an idea.. will be there option to convert image to 2-bit depth size? Black and white option acctualy makes it to gray scale… I’m missing real black and white…

  6. Rick Brewster says:

    Samuel Jack, why would lawyers care anything about this?

    Hans Wurst, I’m just using what I already have in place for PaintDotNet.Native.x86/x64.dll (not to be confused with PaintDotNet.SystemLayer.Native.x86/x64.dll). The code is the same, it’s just built twice.

    Bartek, you mean 1-bit. This isn’t a feature that’s requested very often, nor do I see really much usefulness for it. Probably not.

  7. Johannes says:

    Bartek: Convert to grayscale, then use Contrast/Brightness and turn the contrast slider all the way up. You can then use the brightness slider to fine-tune the threshold between white and black.

  8. Drazick says:

    Those are great news.

    Moreover those new technologies (Direct 2D / Direct Write) seems to make you motivated, which is the most important factor of having a great software :-).

  9. Bastian Hougaard says:

    Interesting. I am wondering how people will react when they cannot install Paint.NET due to it’s incompatibilty with XP, but perhaps the world has changed in mean-time (2011). Well, it sounds like I am safe, using Vista even though I from time to time prefer XP.

  10. Sozo says:

    Looks like you have a good update in the works, I look forward to using it. 🙂

    This is one person who won’t complain about dropping XP support in 2011. Anyone still using XP by then will have more problems than just Paint.NET dropping support.

  11. Michael Ragsdale says:

    (humor) WTF can’t I install this on Windows 98 SE (/humor)

    Seriously, as a Win 7 Pro x64 user, I have no problems with XP support being dropped from Version 4.0, I’m thinking about doing the same for my Book Reader software (although I’m thinking about enforcing a Vista SP2 or 7 requirement for my software – what exactly are .NET 4.0 System Requirements?)

  12. Boude says:

    I think a performance increase would greatly upgrade PDN overall, awesome. But wouldn’t using C++ mean you’d have to compile it on setup, or am I just being a complete idiot now?

  13. Rick Brewster says:

    Michael, I think .NET 4 requires XP SP3 / Vista SP1.

    If you want to do any Direct2D, DirectWrite, or UI Animation stuff then you need Vista SP2 + Platform Update.

  14. Rick Brewster says:

    Boude, the C++ stuff gets compiled on my machine before I hand it out to anyone. Just like any normal app. You’re thinking about NGEN for .NET, which performs the ‘back end’ compilation that the JIT would otherwise have to do every time you run the program.

    JIT is the Just-in-Time back-end compiler that translates MSIL (Microsoft Intermediate Language) into machine code (x86, x64). C# and VB, etc. are front-end compilers that that convert C#, VB, etc. into MSIL. NGEN is an “ahead-of-time” compiler that does (mostly) the same thing JIT does except that it caches the results so that the JIT isn’t even needed. This saves CPU and memory usage when running that code.

    C++ just compiles straight to machine code. There’s no JIT or NGEN involvement.

  15. Olivier says:

    Hi Rick, I’m wondering how you interop with native code? Managed C++, duplicated (per Dll name) DllImportAttribute?

    I once used a trick to avoid duplicating my interop definitions in my C# code: I use LoadLibrary passing it the right DLL name base on the IntPtr.Size test for bitness. The trouble is I have to define delegates, properties… and I only used it on very simple C++ exports signatures (no complex data structures).

  16. Rick Brewster says:

    The interop layer is split into 5 parts, actually. It piggybacks on the existing DLL layout and patterns in Paint.NET.

    In PDN.Base.dll (C#), I declare all the structs, enums, and interfaces. The interfaces are not COM interop definitions, but rather C#-comfortable analogs of their COM counterparts. For example, instead of “GetNameLength()” and “GetName()” methods, it’ll simply have a “Name” property. The structs are defined, where possible, to be blittable. That is, to match the C/COM definition precisely so that only a pointer has to be sent across the native/managed boundary.

    In PaintDotNet.SystemLayer.Native.x86/x64.dll (C++/CLI), I then implement every single one of those interfaces. Each takes a pointer to the respective COM object in the constructor (and does an AddRef, then Release in dtor). I wind up with two parallel namespaces, PaintDotNet.SystemLayer.Native.x86/x64.Whatever. All classes except a root, static ‘factory’ class (in fact it’s a factory that creates the ID2D1Factory…) are internal. These two DLL’s share all the same CPP and H files, but a preprocessor definition ensures they live in two separate namespaces.

    Then in PaintDotNet.SystemLayer.dll (C#), I have a static class that does an if/then/else and calls the appropriate method from either the x86 or the x64 DLL. If you’re paying attention you’ll note that this PDN.SL.dll actually has a reference to BOTH the PDN.SL.Native.x86.dll and the PDN.SL.Native.x64.dll. Because everthing is demand-loaded in .NET, this works (although NGEN will spit out errors, you can ignore them.) SystemLayer is “not for plugins” however, so I still need another trampoline …

    (As a quick implementation aside, the if/then/else has to be split into 3 methods. The first method is the public one, which if/then/elses over to the 2 bitness-specific methods which are marked with [MethodImpl(MethodImplOptions.NoInlining)])

    Then in PaintDotNet.Core.dll, I have the ‘friendly’ root/static/factory class. e.g. when you call “PaintDotNet.Rendering.Direct2D.Direct2DFactory.Create()” (in Core) it calls the static method in SysLayer, which calls the appropriate method in SysLayer.Native.x86/x64.dll, and you get a reference to an IDirect2DFactory which was defined in Base.

    It sounds convoluted, but it actually … and honestly … works really well. In fact, after writing 10,000 lines of interop code, it worked the first time (well, almost). (it’s now over 30 KLOCs of interop code!) Compared to the various alternative structures I evaluated, this one is definitely the best. Including some other details I haven’t gone over, it also preserves both COM and .NET semantics without hiding either, nor making either a burden.

    The other thing this structure lets me do is to put lots of helper stuff and extension methods in Core. That keeps it far, far away from the base interfaces and implementations.

  17. Olivier says:

    Thanks a lot for this detailed explanation Rick. This gives me plenty of ideas to play with. Maybe a pinch of T4 templates may help generate most of the boilerplate code?

  18. Bruce says:

    Any chance of PdnBench making a comeback? Maybe with a few InternalsVisibleTo attributes sprinkled around?

  19. Rick Brewster says:

    Bruce, InternalsVisibleTo only works with strong-named assemblies. Paint.NET doesn’t use strong naming because it doesn’t install anything into the GAC.

  20. Bruce says:

    I think you only need to strongly name your calling app if your main app is strongly named.

    I have used the attribute to allow units tests to call some friend/internal functions in an assembly. In this case I just used the short assembly name syntax.

    [Assembly: InternalsVisibleTo(“Company.Product.Tests”)>]

    Not sure if this is any different between exe’s.

    http://msdn.microsoft.com/en-us/library/0tke9fxk(VS.100).aspx

  21. Rick Brewster says:

    I know how what attribute is; I’ve used it before. I’d have to strong name the DLL containing the classes used by PdnBench, as well as every DLL that it depends (i.o.w., if Core.dll is strong name then I must also strong name SystemLayer, Base, Resources, etc.). I’m not doing that. PdnBench may or may not make a come back, who knows.

Comments are closed.