Optimization for PIC Microcontrollers
This page will help you to optimize your PIC C code. The techniques for optimizing C code on a PIC can be very different to optimizing for a PC - sometimes the complete opposite of what you might think!
You must first decide exactly what you are optimizing for: size or speed. These two goals are sometimes (but not always) conflicting. The second thing you have to decide is
where to optimize. Your program
will probably spend 90% of its time running 10% of your code, so obviously it is important that the 10% it uses most should be the best optimized. This means your inner loops, interrupt service routines, and so on. You probably won't be able to do any profiling
to actually measure where the time is being spent, but you probably know your code well enough to guess where the bottleneck is.
I will mostly be describing PIC-specific optimizations, with perhaps a mention of one or two standard optimization techniques. For more general optimization techniques, consult your favourite search engine!
This tutorial was written by MrZebra, October 2007.
Use Better Algorithms
Code optimization and bit twiddling will only get you so far. The very
best way to optimize your code is to use a better algorihm. This may mean replacing your linear search with a binary search, replacing your
shift register with a circular array, and so on. What you actually do will be entirely depenent on your application - but this is by far the best method of optimization!
Forget your PC Optimization
The PIC has no cache, no branch prediction, no page file, and only one working register. Data locality means nothing on a PIC!
Use Unsigned Char
This is
very important :
don't use
int
as your standard data type! If you need a variable and you know it's not going to go above 255, then use an unsigned char!
for (int x = 0; x < 16; x++) foo(); // BAD!
for (unsigned char x = 0; x < 16; x++) foo(); // GOOD!
Avoid Pointers
That's right! Pointers were your best friend when optimizing for the PC, but on the PIC they are not. Pointers on the PIC are 16 bits long, but the PIC is only an 8 bit processor. This means means that things like pointer addition
(
ptr++
) actually takes TWO instructions
Good:
unsigned char j; x = array[j]; j++;
Bad:
unsigned char *p; p = array; x = *p++;
Avoid Loading Constants
Loading a constant such as
x = 1;
actually takes two instructions, one to load the constant into the working register and one to copy the working register into the variable. The exception is loading zero, which takes
one instruction (CLRF).
x = 0; // Takes one instruction
x = 1; // Takes two instructions
Avoid Overusing Shifts
Using a bit shift is a great way to divide/multiply by a power of two, but the PIC can only shift one bit at a time... so to shift 3 bits requires 3 instructions. Keep this in mind!
x = y << 3; // Takes 3 instructions (plus loading/storing)
Check the Assembly Output
Once you've compiled your program, go to "View Disassembly" on the menu (if using MPLAB... if not, your compiler probably writes it to a file somewhere that you can find). Find your inner loop, and see if the compiler is doing
anything stupid. Keep a copy of your assembly code, tweak your C code, recompile, and compare the new assembly output side by side with the old. This is a good way to tell if refactoring your C code is making things better or worse. If you really can't get
the compiler to write it how you want, consider using inline assembly.
Single Bits are Cheap
The PIC has instructions to set, clear, test and toggle a single bit - use them!
x |= 1; // One instruction
Use Lookup Tables
A standard optimization technique - store things in a table. Look carefully at your memory usage and remember:
free memory is wasted memory. It might go against the grain to create a lookup table for something that could
be easily handled by a
switch
, but if you have the memory to spare then you will increase your speed.
Unrolling Loops
A nice and simple technique to start with: unrolling loops. This simply means that instead of going around a
for
loop n times, you copy the code inside the loop and paste it n times! If you don't know the number of
times you'll be going around the loop, or if it's large enough that copy/pasting the code would be very wasteful, then you can go around the loop in steps by repeating the code that many times in the loop. For example, if you will be looping "n" times and
you know that "n" is a multiple of 4, you can copy/paste the code inside the loop 4 times, and go around the loop "n/4" times. This means you only do a third of the increments and comparisons that let you know when to exit the loop.
Be aware that this method trades size for speed - your code will become larger, but faster. Use this technique very sparingly: it's most useful for things like copying arrays.