Memory optimisation

I am writing a data field and am struggling with memory optimisation, writing for embedded devices is new to me. I have done obvious things like cut out rarely-used functions and extraneous variables, but what advice do others have? What calls or methods use the most memory in this environment? I feel like I am just doing trial and error at the moment.

It seems like using short-lived vars uses less memory, e.g.:
var colour = x;
if (cadence < 100) {
colour = y;
} else if (cadence < 200) {
colour = z;
}
dc.setColor(colour, Gfx.COLOR_TRANSPARENT);
dc.fillPolygon(pts);


vs

if (cadence < 100) {
dc.setColor(y, Gfx.COLOR_TRANSPARENT);
} else if (cadence < 200) {
dc.setColor(z, Gfx.COLOR_TRANSPARENT);
} else {
dc.setColor(x, Gfx.COLOR_TRANSPARENT);
}
dc.fillPolygon(pts);


Why is that? It seems counter-intuitive to me, as the extra overhead of the 'colour' var is needed.
  • "colour" is a simple object (a Number) that is only needed in the scope of the function so it comes and goes, while having the multiple calls to dc.setColor() requires more code for the multiple calls and that always uses memory.

    The sim in 2.1.x added a nice feature - on the bottom line. In 1.x.x, it would show current memory usage and max allowed. 2.1.x added "peak". The "current" value is basically the amount used when your code is idle, while peak is what it uses when running. (the difference between memory that's always used and the memory usage that "comes and goes" in addition to what's always used)

    With 1.x.x, for a 16k DF you might see something like "11k/16k" for memory usage and get out of memory errors and it wasn't easy to see what was happening, but with 2.1.x you get more info like "11k/16k peak: 15.8k" and you know you're in risk of out of memory if you add more functions to the df.
  • "colour" is a simple object (a Number) that is only needed in the scope of the function so it comes and goes, while having the multiple calls to dc.setColor() requires more code for the multiple calls and that always uses memory.


    Thanks. My thinking was that dc.setColor() is only ever called once, in both scenarios, because of the if logic block - you're saying that the complexity of all possible codepaths with dc.setColor() calls is more memory intensive than using the if block to set the 'colour' local variable instead?

    The sim in 2.1.x added a nice feature - on the bottom line. In 1.x.x, it would show current memory usage and max allowed. 2.1.x added "peak". The "current" value is basically the amount used when your code is idle, while peak is what it uses when running. (the difference between memory that's always used and the memory usage that "comes and goes" in addition to what's always used)

    With 1.x.x, for a 16k DF you might see something like "11k/16k" for memory usage and get out of memory errors and it wasn't easy to see what was happening, but with 2.1.x you get more info like "11k/16k peak: 15.8k" and you know you're in risk of out of memory if you add more functions to the df.


    I started with the CIQ 2.1 SDK, so have never known 1.x, but, yes, this is useful. I am writing for the Forerunner 235, so need to keep under 16 kB, but often also simulate with the fr735xt VM, because it has a bigger memory, which is useful for implementing code before then trying to optimize it under 16 kB.
    The view memory feature is marginally useful, but hampered. I think others have mentioned it would be useful if it would show a live, updating view. But that aside, I find it difficult to know what some memory usage is - 'native functions' for instance. And when I have settings/properties, they seem to be assigned strings as well, so I resorted to single-letter variable names to shave a few bytes off. It makes for illegible code!
  • that the complexity of all possible codepaths with dc.setColor() calls is more memory intensive than using the if block to set the 'colour' local variable instead?

    Yes. Calling a function involves finding and pushing arguments (each parameter, the object to call it on, and the function to call) onto the stack, and then making the call. By writing the call three times, you're incurring that overhead three times.

    If the MonkeyC compiler were an optimizing compiler, it could potentially coalesce the common bits of code for you. But it isn't....

    Travis
  • Yes. Calling a function involves finding and pushing arguments (each parameter, the object to call it on, and the function to call) onto the stack, and then making the call. By writing the call three times, you're incurring that overhead three times.


    Thanks, but this is what I do not understand — the three dc.setColor() calls in the example are mutually exclusive, because they are inside an if..else block, so only one call to dc.setColor() is ever made. You're saying the overhead still applies?
  • Yes - all three calls are compiled into the code. so that increases the size of the code (therefore the memory used by the prg). Which one is executed is determined at run time, but the call is compiled in 3 times.
  • Consider these to cases:

    for(var i=0;i<20;i++) {
    myFunction(i);
    }

    and
    myFunction(0);
    myFunction(1);
    myFunction(2);
    myFunction(3);
    myFunction(4);
    //calls with 5 to 18
    myFunction(19);

    Both do the same thing, but the first case will take less memory, as it only sets up for the call once.

    by not using colour, the compiler is setting up for three calls, while if you use colour, it's only one.
  • Excellent, thank you. I will have to go back through my code and re-work with temporary local variables.
  • Thanks for your help with this - I've managed to save over 3 kB! Sadly not quite enough to fit the ANT code for communicating with a Tempe sensor, so I'll have to continue to live in hope that Garmin add the temperature field to the ActivityInfo object for data fields.