How to free up flash space with brutalism

Recently @gapMindful was regretful about not having enough space to add a fancy new feature to a game. I have spent the better part of my long life shaving bytes off of compiled AVR programs (or, at least it feels that way) so I thought I’d take a look and see if I could offer any help.

When I started, the game was 5864 bytes (99% full). With one pass, I was able to get this down to 5674 bytes (%96 full) without removing any functionality or even changing any of the structure of the program. My guess is that with more time it would be possible to reduce the flash size of the program by an additional 25% or more - it is just a matter of how much time you are willing to put into the effort.

Here I’m going to go over a couple of the tricks I used so you can look for these opportunities in your own code when you are getting near to the edge of the cliff.

But first I want to make something clear: these changes do not make the program better - only smaller. @gapMindful’s original code was elegant and a pleasure to browse and, if anything, these changes make that code uglier and harder to reason about and update. We do this out of necessity and not aesthetics. Always write the best code you can… but then be willing to defile it if that is what it takes to get everything you want to fit. It is a harsh little world we live in on this tiny processor!

  1. Say the same thing a different way

The first trick is simple - I changed a set of if statements into a single switch statement…

These two versions of the code are functionality identical, but the second one compiles to be 8 bytes smaller on my machine. A better compiler should do this for us, but we work with what we have.

These ones are hard to predict so really you just get a feel for what changes might result in size reductions, and then you test to see if they actually had the right effect.

  1. Small chips like small arguments

Next up I used simple bytes to replace some cases where the Action struct had been used - especially in argument passing…

image
image

Again, the two versions are functionally identical, but it just turns out that with gcc on this tiny processor, it is much more efficient to pass two bytes than it is to pass a struct.

With a few more additional changes to make Action usage more efficient, I was able to free up 182 more bytes of flash.

TEST TEST TEST

This is not an exact science so it is very important to test every change you make to insure it actually makes the program smaller. Some changes will end up making the program bigger, sometimes alarmingly so.

Conclusion

So these are just meant to show that there are usually opportunities to make code smaller, and it usually just boils down to how much time and effort it is worth investing to find them. Also remember that optimizations that make code less clear do come with a time cost if you have to go back someday and try to understand what is going on - so best to only start the slashing if it ends up being necessary, and even then try to do it as late in the development cycle as practical.

LMK if any questions, and please post any particular interesting examples of productive destruction you wreak!

-josh

5 Likes