In “5 Embedded System Characteristics Every Engineer Should Monitor”, I discussed key embedded system characteristics that in many cases require optimization. These characteristics include system timing, code size, RAM usage and energy consumption. Optimizing each characteristic typically requires different methods and techniques but there are several general tips developers can follow to optimize their embedded software.
Tips #1 – Always create a baseline for comparison
Creating a baseline to compare optimization results against is obvious, and yet, it would surprise you how many times I encounter teams in a hurry that jump straight into optimizing without any baseline. A baseline measurement is important because there are diminishing returns with each optimization cycle. For example, a first pass at optimizing for energy may result in a 20% improvement. The second, 10%, then 5% and so on. Developers need to be able to see this trend and quantify the improvement they are seeing in the system for their time input.
Tips #2 – Set an optimization target
Each optimization pass requires more and more time to squeeze just the smallest improvement from the system. Teams need to carefully balance their time investment and determine if the resultant improvement is worth the time. Trying to go as low as you can go can become addicting and before one realizes, they can be spending weeks optimizing a system that no longer needs to be optimized. Before optimization begins, teams should set a target level that once achieved is good enough for the current application and indicates that the optimization process has completed.
Tips #3 – Get the right tools to make measurements
Optimizing a system can be extremely difficult without the right measurements tools. Optimizing for energy can’t be done if there is not an accurate means to measure the system and microcontroller energy draws. In many cases I’ll see teams fail to separate these two different energy measurements and attempt to minimize microcontroller energy when it can’t go any lower. Developers interested in optimizing for performance might find the Seggar System View Utility, that I wrote about in Hands-on: Seggar System View Utility, to be critical to understand which functions are monopolizing the CPU. Without tools that can accurately measure or a allow developer to peer into the system behavior, there is no point in attempting to optimize a system.
Tips #4 – Use optimization tools
There are many areas that embedded software can be optimized in order to decrease code size or improve performance and in some circumstances there are separate or add-on toolchains that can perform the optimizations. An example optimization tool is the Somnium DRT optimizer that can be used with GCC to optimize code size, energy usage and performance. Sometimes external tools may not be necessary but just the selection of the right toolchain. I recently wrote about Open Source versus Commercial Compilers where I explored the fact that in Coremark tests, under the same test conditions with the same microcontrollers, commercial compilers generated higher scores when compared with open source compilers such as GCC.
Tips #5 – Use compiler attributes and #pragma
In general, I strongly dislike using #pragmas or compiler attributes. Change compilers and there suddenly developers have to go back and rework all those lines of code. Attributes and #pragmas are generally non-portable and can result in software bugs when changing compilers. However, when it comes to fine tuning embedded software, developers will usually not have a choice. Using attributes and #pragma can improve speed, selectively apply optimization to a single function and so forth. So for those reasons, developers intent on optimizing software should get familiar with them but also read Writing Portable Optimizations in C
so that they can understand how they write optimizations that are still portable and won’t come back to bite them.
Tips #6 – Don’t hesitate to experiment
There is no set in stone practice for optimizing a system and developers shouldn’t feel constrained to follow any technique. Sometimes the best way to learn and optimize a system is to put together experiments and just see what happens. When I first started optimizing systems for low power, there was a lot of trial and error associated with it. However, through experimentation and recording results I was able to figure out what works, what doesn’t and what is a waste of resources and time. A simple example is how to Make the most of printf.
Through trying different driver models there are ways to drastically improve the real-time performance a developer gets when using printf which is usually assumed to be far better than it really is.
Tips #7 – Dig into the compiler generated instructions
In some applications that are extremely resource constrained, there comes a time when developers just need to roll up their sleeves and dig into the compiler generated instructions. Selecting the ternary operator over an if/else could potentially be the difference between three or four extra instructions being executed which causes the application to implode. While languages such as C are standard, each compiler optimizes and generates the machine instructions slightly differently and the only real way to know what your compiler is doing is to review the assembly. (Check-out Ternary Operator versus if/else If you are interested in a low-level look).
Applications will drastically vary on their optimization needs. Some applications that are in low volume production may require no optimization at all. Others, where every clock cycle of nano-amp matters, vast amount of time may be spent trying to squeeze every last drop of performance or energy from the system. While each system is different, these tips provide developers and teams with a starting point that can get them on the road to a more efficient system.