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
  • Yeah there's a huge difference because the code in question positions all the elements of a 6 or 7 field "full-screen" data field.

    So there's 6 or 7 labels and values, lines to separate each of the fields, header and footer, etc. There's also code to dynamically resize fonts to fit in a given area, based on the content (although to be fair both versions of the code need this)

    The dynamic code isn't quite as simple as your first example. I started with an open-source example that had been shared on github, and tried to rewrite to be more memory-efficient, but nothing was more efficient than doing static precalculation. The dynamic example took up over half of the available RAM on a device with 16 KB for datafields, IIRC.

    (Because the layout isn't truly "dynamic", it changes in fixed ways based on the device resolution, shape, and fonts.)

  • I'll keep this in mind. Although I do prefer writing logic instead of hard coding positions, sizes and similar for each device...

  • You could write logic to generate the precalculated layouts....

    I wish I had done that from the start, actually. I still would need to adjust some things by hand due to font quirks across families. (Some fonts have a "height" that includes whitespace above the uppermost visible pixel, and others don't).

  • That's why I define my own font wrappers for each device (or actually for each font, some devices do use the same fonts) with custom ascent, descent and font height definitions so that I can use those values for pixel perfect positioning...

  • This sounds like an interesting approach, could you maybe describe a bit more your technique?
    I also define fixed positions based on screen resolution, format, etc. not in a super granular way, but at least based on resolution and screen shape. I definitely don't take into account variations within non system fonts (I don't use system fonts because they vary too much for my use case). This can handle my simple layouts without looking too bad in any one of them, but its not perfect though. Now, for full screen messages, I still do it dynamically, as each line will have a different length based on the screen size, shape, font rendering size, as well as localisation. So I have to calculate each row dynamically. My logic is "heavily" inspired in:

    https://raw.githubusercontent.com/zmullett/connectiq-sonos/master/source/BetterTextArea.mc

    This works much better for me than the default Toybox.WatchUi.TextArea logic (more compatibility at the cost of heavy memory usage unfortunately).
  • For long messages, I have a fairly simple function for that, where in onLayout I calculate how many characters will fit on a line and then "chop" what's to displayed to that, then look for the last space, and chop again there.  I don't take into account the rounding of the screen, but it's really simple.  I don't use layouts, and did this before TextArea using just dc calls.

  • I did write a test app that simply draws something like following:

    As you can see, on a Fenix 6X it seems like the FONT_LARGE does have an ascent value of 34, an descent of 9 (which sum up to 43 font height). And then I try to find out with trial and error how much the desired sign is "off", in my case I want to be able to place texts like "TEST" or "0123" in way that they touch each other, so I now know that I must draw the text 9px higher + the fonts "inner height" is on 43 - 9 * 2 = 25.

    So my own font definitions simple looks like Font(Graphics.FONT_LARGE, 9 /* top space */, 9 /* bottom space*/ ). And by knowing this data you can place texts just the way you want it without device specific logic...

    It's just a lot of tests that needs to be done for each device (until you find the correct top/bottom values) and for each font you want to use...

  • Yes, with all the CIQ devices and all sorts of variations in fonts, you need to do lots of experimenting.  You can avoid some of that if you use custom fonts  You also might find that using TEXT_JUSTIFY_VCENTER can help.

  • Here's the same native fonts on the f5

    and f6

    The little number of the right is the result of dc.getFontHeight(), and the lines that are drawn are also based on that.  You can see on the f5, you white space above or below, but there is on the f6.

  • There's a slew of issues esp when you just start out early on and the wearables have like 16 or 32kb of memory. That means Optimisation to the wazoo.. No such thing as using the LONG version of GRAPHICS.FONT_SIZE_MEDIUM_MILD and what not and just use "1". It was optimise all the way.

    Hard coded Layouts and each time a new device is out, there's another round of layout optimisations to account for differing screen sizes, differing internal fonts. 

    I too try to use the same fonts across diff devices but if its 16KB or 32kB, you Cant due to memory constraints. So it's back t pixel peeping and optimisation again and again.

    I too used to use layouts but then found out the hard way that it eats up memory so had to refactor all of it.