Preserving memory in Monkey C


As for couple of last days I was fighting hard to preserve every single byte of memory, balancing just at the edge... maybe others will find that usefull - and even more - maybe Garmin will work on that to improve as well?

1. Do not use SWITCH / CASE construction. Instead - use IF / ELSE IF construction. It preserves memory, maybe not a lot, but always few bytes or more.

2. This is ridiculous? Do not use CONST variables. It looks like using directly values preserves memory. Don?t like it, but no choice there

3. It is beneficial to create a function whenever 2 or 3 lines of code are repeated. Just use params if required. It will save space

4. Avoid use of dictionaries!

5. For memory consuming operation try to assign to every non used variable NULL ? this will free memory

I'd appreciate - as the whole community - if others would share their findings too!
  • My app is running at 105 of 122kB and I am spending much of my time finding ways to save space.
    I thoroughly endorse your item 3.
    I wold add:
    • Beware of Doubles - they are Objects and are very expensive.
    • Remove all System.println commands except during debugging
    • Use string resources instead of strings
    • Avoid multi-dimensional arrays
  • there have been a few topics about this already, especially Travis has contributed some interesting posts in deep length about it.

    * you can add the -g option to get debug output, this way if you do consecutive compiles you can deduct by counting the instruction lines the thing that has more/less instructions (less instructions normally less memory use). be sure to also check peak object usage though as you can also hit some limits there.

    * if and switch are one thing, a third alternative is to implement your own jump tables. Especially when the cases are consecutive this can be a useful technique (it made me save a few bytes here and there), something to be aware of is you'll use more objects. There's a recent topic about this that goes into more detail (search for switch)

    * you don't know how lucky you are RaceQs, I have to code with a limit of 16k in datafields and I'm juggling with bytes (15.8k peak memory) :)
  • Tried every trick in the book to save space for my app because it has completely open-ended config (it allows the user to specify their own mathematical formula), so the more free memory, the more functional the app becomes.

    I agree with all of the above and I'd add the following. Some of these are terrible programming practices, but they saved lots of memory for me:
    - Don't use enums. (Same reasons as constants). I seem to recall that enums failed silently with bitwise AND/OR in certain circumstances anyway.
    - Don't use functions for one liners, or for code (no matter how complex) that's not repeated. I saved lots of memory by "manually inlining" several functions.
    - Don't use classes where you can get away with static functions or inline code. I saved lots of memory by removing classes that made the code look nice and maintainable. Basically, if you don't need multiple instances, don't even bother.

    e.g. If you have a class that parses/formats a string in interesting ways, consider replacing it with a static function that just takes the same string as an argument.

    e.g. If you have a "circular queue" class that's only used in one function, consider replacing it with an array and an integer that indicates the current position in the queue.

    - If you have a class with no methods (i.e. just a glorified struct), which you're using to pass in or to return multiple values from a function, consider using an array instead.
    - Don't use classes/instance methods where you can get away with global variables. For example, to detect invalid configuration, I originally had a custom exception class (which simply stored an error message string) and a try/catch block at the top-level. I saved hundreds of bytes by getting rid of the try/catch block and class (a barebones custom exception class was already about 200 bytes), and replacing them with an if statement and global variable.
    - Don't use "accessor" instance methods when you can make a field public and access it directly.
    - Continuing the theme of eliminating classes, if you have internal objects of the same type, but with different properties, instead of using a class to keep track of them, consider encoding them as 32-bit integers where certain bits have meaning. (Assuming you have enough bits)

    e.g. instead of:
    class Vehicle
    {
    var type; // enumerated integer for type (1 = car, 2 = truck)
    var weight; // weight in lbs, as an integer
    }

    var fordF150= new Vehicle();
    fordF150.type= 2;
    fordF150.weight = 5000;



    Try this:

    // Vehicle bitmap:
    // 0x00WWWWTT; WWWW = weight, TT = type

    // Option 1
    var fordF150 = 0;
    fordF150 |= 2; // truck
    fordF150 |= (5000 << 8);

    // Option 2 (even better)
    var fordF150 = 0x00138802;

    // Later...
    var vehicle = fordF150;
    var type = vehicle & 0xff;
    var weight = (vehicle & 0xffff00) >> 8;


    (Sorry for the terrible example.)

    Because of the nature of my app (saving memory is more important than maintainability), I broke pretty much every rule of object-oriented programming and maintainable code.

    The worst thing that I did (which probably won't apply to most people) was I encoded a map (from strings to integers) that I only needed at init time (for processing config) into a huge string resource which looked like this:
    "apples:1|oranges:2|bananas:3"
    (Like the above, but dozens of entries.)

    Yes, the init code would scan the entire string for matching keywords, for each token in the user's configured formula. Terrible for performance, but it's a one-time penalty and it saved lots of code space.
  • One thing to consider is doing direct dc calls vs a layout. The biggest impact is probably seen best on devices with a 16k limit for DFs.

    Also, by using build file excludes, you may be able to clean up any "spaghetti code" due to multi-device support. You can make the code device specific at compile time.

    Simple objects vs complex ones helps with both memory and object count (use a float vs a double for example)
  • Being an optimization geek, I thought to take the low hanging fruit and converted all my const to var, as suggested above. However, this increased my memory usage.
    Based on the memory profiler - the particular class went from 252 (1823) to 288 (1859) - just changing const to var for about a dozen attributes.
    So, maybe this is only true for some constants, or maybe it's no longer true.
  • Being an optimization geek, I thought to take the low hanging fruit and converted all my const to var, as suggested above. However, this increased my memory usage.
    Based on the memory profiler - the particular class went from 252 (1823) to 288 (1859) - just changing const to var for about a dozen attributes.
    So, maybe this is only true for some constants, or maybe it's no longer true.


    Hey, the advice above actually refers to replacing const integers/floats with hardcoded constants (integer/float literals), not vars. I would def expect the behaviour you are seeing for the code change you described, since you're replacing one kind of variable with another. (In a lower-level language such as C or C++, using a const might be equivalent to using hardcoded literals, but obviously not in Monkey C).

    Here's a silly example.

    Original code:
    const notPi = 3.14;
    const radius = 5;
    System.println("The area of the circle is not: " + notPi * radius ^ 2);


    New code:
    System.println("The area of the circle is not: " + 3.14 * 5 ^ 2);



    Obviously this is terrible for maintenance (and ease of comprehension) purposes, especially if you need to change the value of a constant, so I wouldn't do this except as a last resort.

    If you must, I'd recommend commenting liberally and, if you ever open-source your code, I'd expect anyone else who reads the source to hate you haha.
  • Yep - that's clear. Thanks. It's good to clarify that.

    Monkey C - I am guessing can't do what C does, because it interprets variables at run-time, so constants result in more code. They also occupy heap space rather than stack space (just my guess).
    I really wish they just used C. The whole idea based on the CTO's speech was to create a highly optimized env. yet here we have run time type checking and such... too much monkeying around :).

    Cheers.
  • TRexOne sorry if I misinterpreted your post. I thought you meant you literally changed "const" to "var", so I'm sorry if I came across as condescending....

    The results you saw kinda surprised me though, so I created a quick test case (simple data field) with the 3.0.0 Beta 1 SDK:

    Code with const:

    * Code size = 236 bytes
    * Data size = 815 bytes


    using Toybox.WatchUi;

    class ciqtestView extends WatchUi.SimpleDataField {
    const x = 5;
    function initialize() {
    SimpleDataField.initialize();
    label = "My Label";

    System.println("x = " + x);
    }

    function compute(info) {
    return 0.0;
    }
    }



    Code with literal:

    * Code size = 233 bytes
    * Data size = 807 bytes


    using Toybox.WatchUi;

    class ciqtestView extends WatchUi.SimpleDataField {
    function initialize() {
    SimpleDataField.initialize();
    label = "My Label";

    System.println("x = " + 5);
    }

    function compute(info) {
    return 0.0;
    }
    }


    The version of the code with the literal takes less memory for both code and data than the version with the const. I tried the same test with that const used twice (two println statements), and got similar results.

    I definitely saved a lot of memory (relatively speaking) by eliminating all of the consts and enums in my code (at the expense of making it very hard to read and maintain).

    Unless I'm missing something, I don't think it's impossible for Monkey C to "inline" all references to a const as if it were a macro and not an object (*), but it probably just goes against the design goals and philosophy of the language. Then again, if consts have to be accessible by outside classes which don't have access to the source of the class where the const is defined, then I can see why it would be impossible. This is the case for consts which are defined in the CIQ API, for example. If only we could have consts that were locally-scoped or "app-scoped". I did notice that private or file-scoped consts are permitted, but you don't save any memory by using them....

    (* According to the docs, it looks like they are on the heap)

    https://developer.garmin.com/connect...uide/monkey-c/
    Like Java, Monkey C compiles into byte code that is interpreted by a virtual machine. Also like Java, all objects are allocated on the heap, and the virtual machine cleans up memory (Java through garbage collection, Monkey C through reference counting). Unlike Java, Monkey C does not have primitive types—integers, floats, and chars are objects. This means primitives can have methods just like other objects.
  • While using literals (as opposed to constants) will save you memory now, it does cut down on the readability of your code. Additionally, a change to the compiler could be made to evaluate expressions with const and enum values at compile time.. keeping the readability and giving the efficiency you're expecting.

    I'm not saying that you shouldn't do this.. do what you've gotta do... but I'd avoid it unless you are really down to counting bytes.

    Travis
  • In my case I saved on the order of 100s of bytes by eliminating consts and enums, which at the time was a significant proportion of the typical free memory available to my app, which allowed me to add requested features (especially to CIQ1 devices). But my app, with its open-ended config (and open-ended memory usage) is a bit different than almost any other app, so I definitely wouldn’t recommend this for most people. (Another thing I did was get rid of most of my classes and I even “manually inlined” certain very simple functions, which are also things I would not recommend for most.)

    I can def see why Garmin did not choose C, especially if they want a low barrier for entry. Although it’s one of my primary languages for work, I can see many issues in the design and standard library which, anecdotally speaking, can cause very subtle problems even for devs who have been using it for decades. I don’t think anyone would design C if they were creating a new language from scratch in 2018.