Is there a better way to select resources?

Hi,

I am using a lot of json resources in my fields so as to allow me to rapidly configure them for a range of different situations. The trouble is, I haven't been able to find a way to evaluate a key for the resources...

EG: In other languages, I might have done:

function getResource(key) {

return WatchUi.loadResource(Rez.JsonData[ key ]);

}

But that syntax won't work, so to avoid a big-arsed and ugly if / switch statement running for several pages, I have resorted to...

function getResource(key) {

return WatchUi.loadResource([Rez.JsonData.K0, Rez.JsonData.K1, Rez.JsonData.K2, ... ][key]);

}

I've had to try similar approaches with programmatically selecting symbols.

But...

THAT SEEMS LUDICROUS!

Is there really no way to evaluate a string to return a symbol or a resource?

G

  • Wow!

    Yes, you are right:

    		var jd = Rez.JsonData;
    		var lookups = [jd.DISPLAY0,jd.DISPLAY1,jd.DISPLAY2,jd.DISPLAY3,jd.DISPLAY4,jd.DISPLAY5,jd.DISPLAY6,jd.DISPLAY7,jd.DISPLAY8,jd.DISPLAY9,jd.DISPLAY10,jd.DISPLAY11,jd.DISPLAY12,jd.DISPLAY13,jd.DISPLAY14,jd.DISPLAY15];
    

    Versus:

    		var lookups = [Rez.JsonData.DISPLAY0,Rez.JsonData.DISPLAY1,Rez.JsonData.DISPLAY2,Rez.JsonData.DISPLAY3,Rez.JsonData.DISPLAY4,Rez.JsonData.DISPLAY5,Rez.JsonData.DISPLAY6,Rez.JsonData.DISPLAY7,Rez.JsonData.DISPLAY8,Rez.JsonData.DISPLAY9,Rez.JsonData.DISPLAY10,Rez.JsonData.DISPLAY11,Rez.JsonData.DISPLAY12,Rez.JsonData.DISPLAY13,Rez.JsonData.DISPLAY14,Rez.JsonData.DISPLAY15];
    

    Gives me a difference in compiled code of (12039 - 12215) or a saving of 176bytes!

  • As I tried to point out above, you can get further savings by storing only the symbols in the array and looking them up within the JsonData object. Here is a quick summary of the data I've gathered for the various techniques proposed here. Decide for yourself.

    //////////////////////////////// Method 1
    // Memory Usage: 7.3kB
    // Peak Memory: 8.6kB
    // Code: 1438
    // Data:  932
    //
    // Total: 1044032 Used: 7640 Free: 1036392
    //
    function findResource(key) {
        var fields = [
              Rez.JsonData.DISPLAY_NONE,
              Rez.JsonData.DISPLAY0,
              Rez.JsonData.DISPLAY1,
              Rez.JsonData.DISPLAY2,
              Rez.JsonData.DISPLAY3,
              Rez.JsonData.DISPLAY4,
              Rez.JsonData.DISPLAY5,
              Rez.JsonData.DISPLAY6,
              Rez.JsonData.DISPLAY7,
              Rez.JsonData.DISPLAY8,
              Rez.JsonData.DISPLAY9,
              Rez.JsonData.DISPLAY10,
              Rez.JsonData.DISPLAY11,
              Rez.JsonData.DISPLAY12,
              Rez.JsonData.DISPLAY13,
              Rez.JsonData.DISPLAY14,
              Rez.JsonData.DISPLAY15,
              Rez.JsonData.DISPLAY16,
              Rez.JsonData.DISPLAY17,
              Rez.JsonData.DISPLAY18,
              Rez.JsonData.DISPLAY19,
              Rez.JsonData.DISPLAY20,
              Rez.JsonData.DISPLAY21,
              Rez.JsonData.DISPLAY22,
              Rez.JsonData.DISPLAY23,
              Rez.JsonData.DISPLAY24,
              Rez.JsonData.DISPLAY25,
              Rez.JsonData.DISPLAY26,
              Rez.JsonData.DISPLAY27,
              Rez.JsonData.DISPLAY28
        ];
    
        return fields[key];
    }

    The reason Method 1 takes up so much code space is because of the repeated Rez.JsonData.<SYMBOL> expressions used to initialize each array element. The assembly to initialize each element in the array is 9 instructions:

    ipush 0
    lgetv 0
    spush Rez
    getv
    spush JsonData
    getv
    spush DISPLAY_NONE
    getv
    aputv

    //////////////////////////////// Method 2
    // Memory Usage: 6.9kB (-.4kB vs Method 1)
    // Peak Memory: 8.3kB (-.3kB vs Method 1)
    // Code:  1094 (-344B vs Method 1)
    // Data:   932 (-0B vs Method 1)
    //
    // Total: 1044032 Used: 7296 Free: 1036736 (Used -344B vs Method 1)
    //
    function findResource(key) {
        var jd = Rez.JsonData;
    
        var fields = [
              jd.DISPLAY_NONE,
              jd.DISPLAY0,
              jd.DISPLAY1,
              jd.DISPLAY2,
              jd.DISPLAY3,
              jd.DISPLAY4,
              jd.DISPLAY5,
              jd.DISPLAY6,
              jd.DISPLAY7,
              jd.DISPLAY8,
              jd.DISPLAY9,
              jd.DISPLAY10,
              jd.DISPLAY11,
              jd.DISPLAY12,
              jd.DISPLAY13,
              jd.DISPLAY14,
              jd.DISPLAY15,
              jd.DISPLAY16,
              jd.DISPLAY17,
              jd.DISPLAY18,
              jd.DISPLAY19,
              jd.DISPLAY20,
              jd.DISPLAY21,
              jd.DISPLAY22,
              jd.DISPLAY23,
              jd.DISPLAY24,
              jd.DISPLAY25,
              jd.DISPLAY26,
              jd.DISPLAY27,
              jd.DISPLAY28
        ];
    
        return fields[key];
    }

    Method 2 removes one spush and one getv instruction for each array element to the following, which reduces the number of instructions per entry to 7:

    ipush 0
    lgetv 0
    spush jd
    getv
    spush DISPLAY_NONE
    getv
    aputv

    //////////////////////////////// Method 3
    // Memory Usage: 6.8kB (-.5kB vs Method 1)
    // Peak Memory: 8.2kB (-.4kB vs Method 1)
    // Code:  1003 (-435B vs Method 1)
    // Data:   932 (-0B vs Method 1)
    //
    // Total: 1044032 Used: 6640 Free: 1037392 (Used -1000B vs Method 1)
    //
    function findResource(key) {
        var fields = [
            :DISPLAY_NONE,
            :DISPLAY0,
            :DISPLAY1,
            :DISPLAY2,
            :DISPLAY3,
            :DISPLAY4,
            :DISPLAY5,
            :DISPLAY6,
            :DISPLAY7,
            :DISPLAY8,
            :DISPLAY9,
            :DISPLAY10,
            :DISPLAY11,
            :DISPLAY12,
            :DISPLAY13,
            :DISPLAY14,
            :DISPLAY15,
            :DISPLAY16,
            :DISPLAY17,
            :DISPLAY18,
            :DISPLAY19,
            :DISPLAY20,
            :DISPLAY21,
            :DISPLAY22,
            :DISPLAY23,
            :DISPLAY24,
            :DISPLAY25,
            :DISPLAY26,
            :DISPLAY27,
            :DISPLAY28
        ];
    
        return Rez.JsonData[fields[key]];
    }

    Method 3 takes the technique as far as it can be taken. It removes the spush and getv instructions from the initialization of the array entirely, and just puts integer constants into it. The instruction count is reduced to 4 per array element:

    dup 0
    ipush 0
    spush DISPLAY_NONE
    aputv

    //////////////////////////////// Method 4
    // Memory Usage: 7.0kB (-.3kB vs Method 1)
    // Peak Memory: 8.4kB (-.2kB vs Method 1)
    // Code:  1015 (-423B vs Method 1)
    // Data:   940 (+8B vs Method 1)
    //
    // Total: 1044032 Used: 6672 Free: 1037360 (Used -968B vs Method 1)
    //
    const fields = [
        :DISPLAY_NONE,
        :DISPLAY0,
        :DISPLAY1,
        :DISPLAY2,
        :DISPLAY3,
        :DISPLAY4,
        :DISPLAY5,
        :DISPLAY6,
        :DISPLAY7,
        :DISPLAY8,
        :DISPLAY9,
        :DISPLAY10,
        :DISPLAY11,
        :DISPLAY12,
        :DISPLAY13,
        :DISPLAY14,
        :DISPLAY15,
        :DISPLAY16,
        :DISPLAY17,
        :DISPLAY18,
        :DISPLAY19,
        :DISPLAY20,
        :DISPLAY21,
        :DISPLAY22,
        :DISPLAY23,
        :DISPLAY24,
        :DISPLAY25,
        :DISPLAY26,
        :DISPLAY27,
        :DISPLAY28
    ];
    
    function findResource(key) {
        return Rez.JsonData[fields[key]];
    }

    Method 4 uses a tiny bit more code space than Method 3 (12 bytes), but avoids the need to dynamically allocate and initialize memory for the array in question. This means it uses a tiny bit more code/data space than Method 3, in exchange for a reduced runtime cost.

    According to the Memory Viewer, the lookup array itself is only 165 bytes, so keeping it in memory is not a problem. If you start running into issues and need to trim down, it might make sense to allocate and initialize that array for every call, but you have to remember that the call will use up an additional object handle and 165 bytes... you may need to release an object somewhere else just to make enough resources available to make the call.

  • Now that post, right there, made this whole thread worthwhile!

    Thanks for the detail - it makes lots of sense and sparks a few ideas. 

    G

    ETA:

    Perhaps off-topic, but it seems relevant:

    Is that additional spush a factor in why using a MonkeyBarrel is so expensive?

    Seems like there is a chunk of memory consumed simply from using the same code in a barrel. 

    A tiny portion of which might be from calling Barrel.method() rather than method() (one more spush, as I understand it) but if the compiler also needs to spush to locate things inside each method the effect would be worsened.

    Basically, the question becomes:

    Am I better having large objects, like entire classes, or tiny building blocks, like oft-used small methods, in my barrels?

  • And a secondary question:

    If I wanted to reuse symbols, for example, two discrete sets of JsonData like Rez,JsonData.ListA[:symbol] and Rez.JsonData.ListB[:symbol] both using similar symbols, is that even achievable? (Not yet seen a way to collect JsonData into subgroups and it might well not be supported.)

  • And a third question (sorry!);

    Is there a way that I can view compiled assembly instructions to work any of this out myself, or is that all stuff you just have to work out from a start point of knowing how code works at a low level? 

  • Here's something interesting to note about barrels.  I use them quite a bit - any code I share between apps is in a barrel project (I have about 15-20 barrel projects)

    The way I do barrels is build them so that they can be used as a barrel, or the .mc/resources can be accessed directly from another project using an eclipse link. (new>file>advanced>Link to file in file system)

    In one case I'm using 6 different barrels.  Memory usage for a 645 target is a bit over 61k.  If I access those mc files with links instead (not changing a line of code), memory usage drops to a bit over 58k.  (it dropped a bit each time I switched a barrel to a link).  So, about 3k saved by not using barrels without changing a single line of code.

    If you look at a barrel, it's actually just a zipped version of the barrel project.

    Eclipse Links are an issue if you use jungles so may not be possible for your apps

  • Is there a way that I can view compiled assembly instructions to work any of this out myself

    You can open up the Eclipse preferences and add -g to the Compiler time options field. (on MacOS go to Eclipse > Preferences > Connect IQ > Compiler)

  • It's the same on windows as on MacOS

  • If I wanted to reuse symbols, for example, two discrete sets of JsonData like Rez,JsonData.ListA[:symbol] and Rez.JsonData.ListB[:symbol] both using similar symbols

    You can't really do exactly what you're wanting because there is only one JsonData object in the Rez module, and the members in the JsonData object are the <jsonData/> resources that you defined in the XML.

    You could make ListA and ListB jsonData resources, and then define them to be arrays, something like this...

    <resources>
      <jsonData id="ListA">
      [
        "ListA.0", "ListA.1", "ListA.2"
      ]
      </jsonData>
    
      <jsonData id="ListB">
      [
        "ListB.0", "ListB.1", "ListB.2"
      ]
      </jsonData>
    </resources>

    And then access the fields after loading the entire JSON array into memory, like this:

    function getJson(symbol) {
        return WatchUi.loadResource(Rez.JsonData[symbol]);
    }
    
    // to access the `idx` property in each list...
    var valueListA = getJson(:ListA)[idx];
    var valueListB = getJson(:ListB)[idx];

    You will pay the cost of allocating and loading a JSON array for each getJson call, but it will do what you want. If you need to make multiple calls, you could just cache the result array as needed.

  • Is that additional spush a factor in why using a MonkeyBarrel is so expensive?

    As has pointed out, there is some overhead with using Barrels. I'm not exactly sure what all of this overhead is, but it isn't just the module qualifiers that you use. There are per-barrel resource tables, and functions for initializing them (even if you have no resources in your barrel) that will take up some code space. You can see all of this if you look at the disassembly.

    Of course, using the module qualifier will add the two instructions (spush, getv), but I thought you could avoid doing that all the time with a using BarrelName clause at the top of your file. If this does work (I don't have a lot of experience with barrels, so I don't know for sure) there will likely be a runtime cost for us to locate the symbol that you're referencing. i.e., if you say Barrel.symbol, we know to look inside the Barrel module for the given symbol. If you just say symbol, we have to search for it in the current scope, the parent scope... the global scope, and the barrel module. This search can be quite costly.