Performances DOs and DONTs

Hello CIQ team,

When looking to avoid common CIQ development pitfalls and optimize apps for best user experience, one has to go through many different resources: the programmers guide, this forum, video presentations scattered here and there, etc.

Personally, I would think a dedicated chapter in the programmers guide would be ideal. But maybe we can start in a dedicated forum post (this one) and eventually gather all the information in one place ?

Examples given (as far as I understood; corrections welcome):

Ui.View.findDrawableById():
vs Speed/Battery: this can become a costy operation if a layout has many drawables (and all drawables IDs must be string-compared in turn); this can conventienly be cached after View.setLayout() is called
vs Memory: layout drawables are loaded when View.setLayout() is called; caching only creates another reference to the already in-memory object and has minimal memory impact

Ui.loadResource():
vs Speed/Battery: this is a costy operation, especially for large resources like bitmaps or fonts; the result really ought to be cached and "freed" respectively in View.onShow() and View.onHide()
vs Memory: runtime memory will be allocated when Ui.loadResource() is called and "freed" (garbage-collected) once all referring variables are set to null; strings behavior is unclear, especially given the changes introduced in CIQ 2.4 [to be further clarified]
NB: This should be used even for XML-defined strings; using Rez.Strings.XYZ directly in Lang.format() constructs leads to integer numbers being displayed rather than the string themselves [bug?]

Custom fonts:
vs Memory: custom fonts uses as much runtime memory as the corresponding *.fnt/*.png files; one must: 1. limit the quantity of glyphs to those really needed (you might want to anticipate translations and yet-unused accented characters, though); 2. favor bitmap (2-color) glyphs to aliased ones (with much larger *.png file)
vs Speed/Battery: see Ui.loadResource() above

XML layout vs "hardcoded" Graphics.Dc.draw*():
vs Memory: XML layout objects really do take their toll on runtime memory; when memory becomes an issue, using Graphics.Dc.draw*() calls in ad-hoc annotated functions are the way to do, as the cost of convenience
vs Speed/Battery: see Ui.View.findDrawableById() above

Prefer if/else over switch/case:
vs Memory: strange as it sounds, switch/case stanza uses significantly more memory as the if/else equivalent [bug?]

Multi-dimensional arrays():
vs Memory and Speed/Battery: MonkeyC arrays are objects which creation and memory-overhead cost must not be overlooked. In particular, when dealing with multidimensional arrays - which are 1-dimensional arrays of 1-dimensional arrays of ... - one is better server by keeping the first dimension lower than the second dimension than ...; e.g. given a two-dimensional {n} x {N} where {n} << {N}, better have an array made of {n} arrays of {N} elements than the opposite

Avoid nested menus:
vs Memory: nested menus may provide an elegant way of organizing settings and the like but, as each sub-menu is open thanks to Ui.pushView(), the parent menu(s) is(are) kept on the views stack along all the (memory) resources they use

XML resources IDs:
vs Memory: XML resources IDs are strings; in order to retrieve some of those resources at runtime (layout drawables, application properties), one uses those string IDs in the source code, resulting into corresponding static string objects defined in runtime memory; if saving a few hundered bytes becomes a necessity, one could look into using "integer" (string) IDs in the XML resources and matching constants or enums in the source code.
vs Speed/Battery: string comparisons are not the least costy operation; the shorter the strings the better
PS: I understand such optimization may not be applied to properties, which must be identifiable unequivocally across different builds of the application


Other DOs/DONTs ?

Best,

Cédric

EDITED: Thanks Travis and Jim for your responses; original post edited accordingly
  • It is tough to give hard and fast rules about the right way to do things. The answer really depends on the application. I would agree that it is important to tell people what the costs associated with something are, and then allow them to make some decisions about how to write their applications.

    Ui.View.findDrawableById():
    vs Speed: this is a costy call and its result should be cached in the View's onShow() method
    vs Memory: ? are drawable resources loaded in memory at the time of the call or initially at app start (and this app call only leading to an additional reference to the memory object) ?


    This call isn't necessarily costly. It does a linear search through the drawables in the layout specified in onLayout(). If you don't have many drawables in the layout, or you only need to search the list every once in a while, it isn't a big deal. I would personally avoid searching through the list multiple times in a draw, but other than that it is probably fine.

    No additional memory should be used if you cache a reference to the drawable retrieved by findDrawableById(). Objects in MonkeyC are reference counted, so as long as the layout that contains the drawable is not unloaded, having the reference to the drawable in that layout will come at no additional cost (except for the memory required to store that reference itself).

    PS: caching can not be performed in View's initialize() since the onLayout() must happen first.

    Technically, it can't happen before setLayout is called.

    Ui.loadResource():
    vs Speed: ? should the result of this function be cached; better in View's initialize() or on Show() ?
    vs Memory: ? are string resources loaded in memory at the time of the call or initially at app start (and this app call only leading to an additional reference to the memory object) ?

    Resources should probably be cached, but it really depends on how often the call is made and the cost of loading the particular resource. If the call is made every time the view is drawn, and the view is rarely drawn, then there may be no need to do the caching. If the view is rendered frequently (is transitioned on screen (note that view transitions typically do 10 or more draw calls in a half of a second), then it should probably be cached.

    Typically you'd load the resource in [url="https://developer.garmin.com/downloads/connect-iq/monkey-c/doc/Toybox/WatchUi/View.html#onShow-instance_method"onShow()[/url] and unload it in onHide() as documented. Developers should be aware of the view lifecycle and that for some application types, onHide() is not supposed to be called.

    I don't believe that resources should be getting loaded at app start. They should be getting read in at the point you make the call to Ui.loadResource(). As I mentioned above, you should probably cache resources.

    PS: I found that this should be used even for XML-defined strings (Rez.Strings.XYZ); in some cases, not doing so leads to an integer number being displayed rather than the string itself

    I might need to see some code, but this sounds like it may be a bug.

    XML resources IDs:
    vs Memory: XML resources IDs are strings; in order to retrieve some of those resources at runtime (layout drawables, application properties), one uses those string IDs in the source code, resulting into corresponding static string objects defined in runtime memory; is there anyway to optimize/avoid this (e.g. using symbols rather than strings when addressing those XML resources
    vs Speed: see Ui.View.findDrawableById() above
    PS: I understand such optimization may not be applied to properties, which must be identifiable unequivocally across different builds of the application

    I'm not sure I know the answer to this. You should be able to use numbers to identify the resources, and then use a constant with a reasonable name to map to that number in the code, but I'm not sure this is the best way.

    Custom fonts:
    vs Memory: ? it seems each custom fonts used uses as much runtime memory as the corresponding *.fnt/*.png files; right ?
    vs Speed: see Ui.loadResource() above

    You want to keep the size of the custom font file as small as possible to reduce the size in memory. That is a hard and fast rule that you can live by. If you don't need a glyph in the font, then eliminate it.

    You want to load fonts when they are needed, being sure not to unnecessarily load/unload it frequently. This is no different than other resources. You'd typically load in onShow() and unload in onHide(). If you load the font in onLayout(), you need to be carefully not to unload it in onHide() as onLayout() isn't called again when the view is shown.

    In some cases, it may make sense to load the resource if it isn't yet loaded...

    function onLayout(dc) {
    myFont = Ui.loadResource(Rez.Fonts.MyFont);
    }

    function onUpdate(dc) {

    // this can happen if the view was hidden, and now it is shown...
    if (myFont == null) {
    onLayout(dc);
    }

    // use myFont to draw
    }

    function onHide() {
    myFont = null;
    }


    Travis
  • Like Travis said, there may not always be hard and fast rules.

    I know one thing not mentioned is arrays (especially large ones), that he and I have both posted about probably 50 times in the past- how to define them to reduce object counts, simple object vs complex objects, etc.

    With using layouts in general, there are cases where you may want to use direct dc calls instead, with the most common example being data fields on 1.x devices where you only have 16k. Doing with direct dc calls could free up that few hundred bytes you need.

    With custom fonts, only include what you need, and use filter where it's needed. For a font to show time, the only things you need are 0 to 9, and often a colon for example. Also the bigger the font, the more memory it takes. If you're looking at using custom fonts, HermoT did a developer blog about Custom Font Tricks.
  • Interesting topic. I would ask further about what is a battery impact of: 

    1. partial display updates (and the cost of the conditional logic) vs. simple redrawing the whole display all the time
    2. precomputation of the more complex calculations like positioning by sin/cos/divisions/for-cycles vs. memory added memory consumption
    3. displaying bitmaps vs. drawing simple symbols from graphic primitives
    4. battery impact of background data loading by makeWebRequest

    How to understand the limits where one is better than the other? 

    And the most importantly: how to measure the battery impact if I want to compare two approaches? 

  • Regarding nested menus, I completely agree with the memory impact. Specially since new devices keep having the 59,9kb widget memory restriction (I'm looking at you Enduro).
    I found a work-around by passing weak menu references to the menu delegates. These are then used to:

    1. Remove both sub-menu and parent menu items from memory when a new view is loaded (e.g. navigate to a picker).

    2. Remove the menu items from memory on "back" action from the Menu.

    This greatly helps keeping things under control when you want to have a user friendly menu system, that also looks good.

  • Another approach would be to use switchToView (instead of popView or pushView) to handle switching between nested menus. You do have to keep track of the "pushed" and "popped" menus with your own little stack, though.

  • Unfortunately not all devices support "switchToView", but yes I use it everywhere its compatible (yay .jungle - it is a jungle maintaining all the different devices!).

  • switchToView with native view is only available on devices with CIQ 3.1 or later.  From the 3.1.0 change log:

    • Allow switchToView() to work between native pages, and from non-native to native pages.
  • Is there a case where using layouts makes sense? The memory difference is pretty big. I used them at first because that's what you get in the tutorials, then reality hits and you need to refactor the whole application to get rid of them for both flexibility and memory optimisation. If I was heading the Monkey C development I would for sure HEAVILY invest in either a static code analysis plugin or a linter that optimizes your written code into the least possible footprint. All of the nice things like constants need to be replaced by their literal values to save memory manually, how can this not be optimised? I guess the layouts could generate similarly optimised code that would do the direct dc calls. It puzzles me why this is not done yet given that memory usage is so crucial in Monkey C, specially with new devices coming out still only supporting 59,9kb of memory for widgets.

    Ok, this kinda developed into a rant, sorry. But sometimes it gets frustrating when you want to support the platform and feel no one is listening.

  • I feel ya man. I've felt the same way, just as a CIQ hobbyist. It's almost like we need another language which can be transpiled to IQPL, so we can have efficient constants, etc. My biggest pet peeve is the fact references to string resources always consume app memory (if you load at least one resource with loadResource), which means that each string you use for app settings wastes memory (which is at a premium for 16 KB or 32 KB data fields). String resources which are only used by the CIQ store (settings and FIT contributor properties) should not consume app memory, IMO. It's bad enough that for one of my data fields, 32 KB device users get a nice drop-down list for a "theme color" settings, and 16 KB device are asked to manually enter a CSS code. :/

    BTW, regarding layouts, I found that even code which dynamically lays out items (e.g. for a full-page data field which shows 6 values) is far less memory efficient then writing your own static layout system (with precalculated layouts for different devices).

    I have the layout data in bit-packed arrays which are stored in JSON resources when available, and a single function which consumes the data and outputs various elements.

    Sometimes I feel like there's a lot of reinventing the wheel here...

  • You mean, instead of calculation device specific data at runtime you do precalculate them for each device and just load them?

    So instead of following

    var x = w / 2;
    var y = h / 12;
    // draw line 1 at x, y
    
    y += h / 6;
    // draw line 2 at x, y
    
    y += h / 6;
    // draw line 3 at x, y
    
    // and so on

    you do something like following

    var x = Device.POSITIONS[0][0]
    var y = Device.POSITIONS[0][1]
    // draw line 1 at x, y
    
    x = Device.POSITIONS[1][0]
    y = Device.POSITIONS[1][1]
    // draw line 2 at x, y
    
    // ... and so on

    Is there really a noticeable difference in such a simple use case?