WatchUI.loadResource() overhead


Maybe I'm missing something, as a new CIQ dev, but does loadResource() incur a permanent overhead? I've seen this with both string resources and jsonData.

Say I have a string that's roughly 400 chars which is only needed in initialization of an app. It's a 1D lookup table, to save memory....

I want to be able to load this data from a resource, and unload it when I don't need it anymore. But I've found there's practically no difference in declaring that string as a const and loading it as a resource, then unloading it. Obviously if I never load the string, I do save RAM, but I need to load it once.

To get good numbers, I started from 0 with a simple data field and did a test with a 350 character string. The only code difference between my test cases is one function call and a variable declaration.

Here are my test cases and numbers (in the simulator, for 230)

Case A: Run app with no string in code or resources.
Memory [current | peak]: 3.8 | 4.2

Case B: Add 350-char string as static const class var
Mem: 4.2 | 4.6

Case C: Remove const var and add to resources [redundant, I know]
Mem: 3.8 | 4.2

So far there's nothing unexpected.

Case D: Load string from resource, then immediately free it [set var to null]
Mem: 4.0 | 4.4 !
Where did that extra 200 bytes come from? (In my real app, where I actually use the string, I see no difference between case B and case D).

Case E: Load string from resource, never free it
Mem: 4.4 | 4.8 !
This is more memory-inefficient than just using a variable in code.

Maybe I'm missing something, so I've included my code below.

Replace square brackets with parentheses below:
using Toybox.WatchUi as Ui;

class memorytestView extends Ui.SimpleDataField {


function testCaseD[]
{
rez = Ui.loadResource[Rez.Strings.bigstring];
rez = null;
}
function testCaseE[]
{
rez = Ui.loadResource[Rez.Strings.bigstring];
}
var rez;
//static const caseBVar = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";

// Set the label of the data field here.
function initialize[] {
SimpleDataField.initialize[];
label = "My Label";


/// 5 test cases

/// =============================
/// Case A [run as is]
/// Memory usage: 3.8, peak: 4.2

/// =============================
/// Case B: uncomment caseBVar above
/// Memory usage: 4.2, peak: 4.4

/// =============================
/// Case C:
/// Comment out caseBVar above
/// Add the following to resource file:
/// <string id="bigstring">01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789</string>
/// Memory usage: 3.8, peak: 4.2

/// =============================
/// Case D: Load bigstring from resources, free it [uncomment line below]
/// Memory usage: 4.0, peak: 4.4
//
//testCaseD[];

/// Case E: Load bigstring from resources, don't free it [uncomment line below]
/// Memory usage: 4.4, peak: 4.8
//
//testCaseE[];

}


function compute[info] {
return 0.0;
}

}
  • I'm seeing a 696b used after loading the string, and 328b after releasing it.

    It almost seems like the string is loaded and cached by the framework, but not as a ConnectIQ string. This would mean access to the string via the resource wouldn't require re-loading from flash, but might mean that a copy is made and handed off to the application for use. Once the copy is in the application, you can pass it around without wasting memory because of the reference-counting system employed by the framework, but the string held by the framework remains.

    Travis
  • Yeah, it's almost like there needs to be an unloadResource() API call.....
  • Yeah, it's almost like there needs to be an unloadResource() API call.....

    IMO, the framework should not be caching a copy of the string internally. If that is what is going on, it should be stopped. The application should get to decide if data is cached and for how long.

    All that said, I've not tested on a device yet, and my feelings about how the system could work may not be taking into account some requirements on the behavior of the framework. Ultimately, it is up to the folks at Garmin to decide how this should work.

    Travis

  • So I gathered a bit more information. The overhead has nothing to do with the contents of the string (as long as you free it, of course). You could have a 1-character or 1000-character string, there's no additional overhead for current memory, after you've released the string. In my case, it was just a coincidence that the overhead is about the same size as my string.

    As far as I can tell, the overhead consists of 3 objects that get created (and never freed) if you call loadResource() even once.
    - 36 byte object (no members)
    - 48 byte object with 1 int (36 + 12*1)
    - Object: 36 bytes + (12 * number of string resources)

    You can try this out by adding several blank strings to resource.xml, in the test case. If you add ten strings, the overhead will go up by 120 bytes. If you never call loadResource(), none of those three objects appears in the memory viewer. You can also change "bigstring" to a blank string. It makes no difference for current memory if you free it. (Obviously peak memory will be affected)

    Of course, in a real app, you have to have string resources for settings labels, prompts, etc. Which is why the overhead is greater in my real app. My app has 12 string resources, 10 of which are absolutely necessary, due to the fact that you're not allowed to hardcode strings in settings.xml (for good reasons). 240-264 bytes of overhead seems like a huge price to pay for loadable temporary resources, on CIQ1 devices.

    Also, the same objects get created if you call WatchUI.initResources() (which is apparently called by loadResource()).

    Really need a destroyResources() or freeResources() API call....