Depending on which operations are being used, that’s generally correct.
Unions are good, a tagged union is even better.
If they ever do another C++11-scale overhaul I hope they add syntax to make tagged unions easier.
It would save some space, but I’d be surprised if it saves a large amount.
When you factor in null checks and array indexing, I think the difference between a table index and a function pointer is probably minimal.
I think one is probably better for saving RAM and the other is better for saving progmem.
(I haven’t checked that because function pointers are quite rare in Arduboy code. I think I’ve seen used about 2-3 times before.)
Macros are a bit of an odd demon.
One of the things I did on Dark & Under to save us memory was to replace the Arduino macro abs
with a template function absT
. I can’t remember how much it saved, probably less than 10 bytes, but that added up to a fair sum because of how and where we were using it.
You’d think that the two approaches would result in the same code, but actually gcc seems to get on better with inlining/optimising functions than it does macros.
I think part of the issue is that because macros are just pasted in, they end up complicating the expression to the point where the compiler can’t do much with the result, or doesn’t notice the repetition so it ends up doing something twice.
(And rarely it actually has to do something twice because of the language rules about sequencing operations.)
On the other hand, if the compiler is looking at a function call instead of the result of a macro expansion, it has an easier time reasoning about things because it sees a function as an input-output situation rather than a chain of operations.
That’s probably what happened. The point at which functions stop being inlined varies with how often they’re used, but calling a function is surprisingly expensive so a lot of the time functions do end up being inlined.
I’ll look at some more stuff later, possibly after you’ve cleaned up a bit if you’re definitely going to be cleaning up since that would help a lot with knowing what changes can and can’t be made, but for now the one change I have found is in person.cpp
Replacing:
switch( eyes ){
case 0: eyeSprite=eyes1; eyeMask=eyes1_mask; break;
case 1: eyeSprite=eyes2; eyeMask=eyes2_mask; break;
default:
case 2: eyeSprite=eyes3; eyeMask=eyes3_mask; break;
case 3: eyeSprite=eyes5; eyeMask=eyes5_mask; break;
}
With:
const uint8_t * eyeLookup[] = { eyes1, eyes2, eyes3, eyes5, };
const uint8_t * eyeMaskLookup[] = { eyes1, eyes2, eyes3, eyes5, };
const uint8_t * eyeSprite = (eyes < 4) ? eyeLookup[eyes] : eyeLookup[2];
const uint8_t * eyeMask = (eyes < 4) ? eyeMaskLookup[eyes] : eyeMaskLookup[2];
Note that if you put eyeLookup
and eyeMaskLookup
outside of the function you get 28276 bytes (98%)/2170 bytes (84%)
, but if you put it inside the function you get 28364 bytes (98%)/2162 bytes (84%)
, so it depends on whether you want more progmem or more RAM. Either way you’ve regained a small chunk of progmem.
I haven’t actually checked if the result is the same, but theoretically it should be.