One of the most common things I have noticed in my experience as an embedded software engineer is that Desktop programmers; especially .Net desktop developers believe they can build embedded apps with little or no change to their coding style.
Regrettably this isn’t always the case, and can result in some shocking performance hurdles that have to be overcome.
I hope to explain a few of the common issues that blight embedded .Net programming and ways you can avoid them.
There is no Native Pre-Compiler for the .Net Compact Framework like there is on the Desktop, you can not optimise your applications by having everything Pre-JITed and thus save a few milliseconds at runtime when the CLR would be JITing the MSIL. Microsoft says No (for now), moving on.
The Garbage Collector
The evil necessity of the Managed Code world, use it wisely and it can be your best friend, however it is usually abused and can become your worse enemy.
The garbage collector has been simplified in the Compact Framework world, and usually means it can be called at the most inconvenient times due to its built in settings. An example of this is that a full garbage collection takes place whenever the application loses focus – great in the CE world to return memory for the application now in focus. When this becomes a problem is if you take the desktop view of loading everything at startup and then hiding away until use – everything will be loaded and then disposed of straight away, whoops!
Fixes for this includes rearranging what is initialised at startup, pinning objects in memory that you want to keep or are expensive to initialise, those that are cheap to create but are expensive for memory, you could consider using WeakReferences.
One big big mistake people use is calling GC.Collect() …big mistake. It is very rare that I have seen someone smarter than the CLR. Just sit back and let the Garbage Collector do its thing. Just code in a way that means that it isn’t that often!
Multiple Concurrent Applications
A common issue for desktop developers is isolation between various parts of the application, usually this can mean different parts of the solution running as separate applications, sometimes concurrently. This automatically creates the issue of the CLR handling more than 1 application and the possible need to communicate between the two or more parts.
The CLR has be launched and create an application domain for each application that is running, this means performing security checks, initialising memory structures, creating a separate garbage collector / CLR thread and get the whole thing running. This takes space and more importantly time.
Therefore if possible just create 1 application, this minimalises these issues. If not possible, try creating a single ‘watchdog’ style application which then loads all the other applications into the same application domain (security possible considered) – this removes the need for additional security checks and means the processes can share a single Garbage collector instance.
For cross application communication, this can be slow! Usually via message queues. If you are using the same Application domain, the CLR does not need to perform security checks thus saving time. If you are using the same process you can push messages onto a different threads dispatch queue, thus it will get processed at the next available instance.
PInvoke & COM
Don’t! PInvoke, and especially PInvoking a COM object should be bywords for SLLLLOOOOWWWW! and I mean really slow, the CLR has to work overtime performing checks and working with the CCW to wrap and detect different variable types.
Whenever possible reduce or remove the need for PInvoking. If it is required, change calls to be as ‘chunky’ as possible, where lots of data is transferred using as few calls as possible.
Another way is to wrap a native object in a facade that handles most of the work (especially to a COM object) and only call it when required.
General CLR Hints & Tips
Marking a class as sealed can be a last ditch hint or miss (usually miss) approach to squeezing a tiny amount more performance more out of class allocations. When a class is initialised, the CLR has to do some checks, by marking the class as sealed some of these checks are removed, however this is partly cached by the CLR anyway so it can sometimes be a one-trick pony.
Virtual Methods are great when you want to obverscate things and have the implementation abstracted away from the underlying interfaces. But it quite obviously creates an additional layer of abstraction for the CLR, meaning more MSIL code. Avoid if possible.
The .Net Compact Framework Compiler is smart, however it is not as clever as its big brother; the full .Net compiler. Support for inlining and built in optimisations is heavily limited. Inlining will only occur on functions of a certain size or below, so unfortunately simple, large functions that are repeatedly called won’t see any improvement. It may be worth performing manual inlining. Remember making a function call in managed code costs MSIL code!
More hints and tips coming soon…