No announcement yet.

exitTo() Out Of Memory Error

  • Time
  • Show
Clear All
new posts

  • exitTo() Out Of Memory Error

    My yacht racing app is too large to run in 124kB, so, I have split it into two components: STARTING , which is operational in the period up to the starting gun, then RACING takes over for the actual race.

    I use exitTo() to link the two components.

    In testing, the RACING app crashes with out Of Memory Error when it is invoked from STARTING, even though there is plenty of memory for the individual components run independently. RACING runs independently with Peak Memory of 102 Kb.

    However the crash is intermittent: ~90% on the device and ~10% in the simulator (video link here ). I have of course been at pains to ensure that the the test environments are identical.

    I rebooted the Mac and ran in the same manner - with no crash. (VIDEO HERE ) showing the second app running correctly with a PEAK memory of 105Kb.
    I can't show the console output as it's the exited to app that's crashing, but when I run it on a physical device I get:
    ERROR: Out Of Memory Error
    DETAILS: Failed invoking <symbol>
    STORE_ID: 00000000000000000000000000000000
       @PC = 0x30000dad
        @PC = 0x30000e64
     /Users/.../Documents/watchApp/raceQs_Racing/source/ (onStart:27)
    Error: Out Of Memory Error
    Details: 'Failed invoking <symbol>'
    Time: 2019-01-10T02:31:55Z
    Part-Number: 006-B2697-00
    Firmware-Version: '12.00'
    Language-Code: eng
    ConnectIQ-Version: 3.0.7
    Filename: QS_RACE
    Appname: QsPlus
     - pc: 0x30000f9f
    It seems to me that there may be a latency issue: the first app has not completely cleared memory before the second app is loaded.

    I can't think of any way to delay the loading of RACING since I lose control on the execution of exitTo(). Any ideas?
    Last edited by RaceQs; 01-10-2019, 12:05 AM.
    raceQs sailboat racing app.

  • #2
    You can free memory setting unused vars to null.

    Perhaps you can win some time setting global objects to null before the exitTo() ...
    Current Fenix5X + Tempe + Foot Pod
    Previous Fenix2, Fenix3
    My apps


    • #3
      What you are implying isn't really possible since applications do not share their memory pools in any way. Allocations from one application cannot impact the memory usage of another.

      You might want to make sure the data being passed from the exiting application to the new application isn't larger than you expect it to be. This is the main difference you would have between starting it on its own, and exiting to it.


      • #4
        Originally posted by Brian.ConnectIQ View Post
        Allocations from one application cannot impact the memory usage of another.
        THat's very encouraging
        You might want to make sure the data being passed from the exiting application to the new application isn't larger than you expect it to be. This is the main difference you would have between starting it on its own, and exiting to it.
        OK thanks, I'll investigate.
        I am passing a single but fairly complex Dictionary.

        raceQs sailboat racing app.


        • #5
          Brian.ConnectIQ I have sacrificed some functionality and reduced the size of the passed Dictionary which appears to have resolved the problem.
          The receiving app is now reporting a Peak Memory of 90 Kb.
          I am still concerned that I may have had to sacrifice the functionality due to a deeper issue.
          When I took a very coarse look at the size of the dictionary ( with .toString().length()) it reported around 12 Kb, but that would have been an inflated figure as the vast majority of the variables were integers.

          So it hasn't really answered two questions:
          1. When it ran successfully on the sim, the receiving app was using 105 Kb which is ~10Kb under the limit and it was passing the same Dictionary as when it failed. When I run the receiving app directly, the Peak Memory is 89 Kb.
            Why does a 105Kb app generate an Out Of Memory Error?
          2. The crash, on the sim and on both devices was intermittent. Why intermittent?
          raceQs sailboat racing app.


          • #6
            Sorry to jump in here, but a dictionary uses much more memory than the string that is used to represent it. For one thing, with a dictionary you can instantly look up values by keys, which is impossible with a single flat string, so it's clearly a more complicated structure. For another, each of the key strings has the overhead associated with strings -- the smallest string in CIQ still takes up 10 bytes or something, IIRC. Even if all your keys were one character, there's still a ton of overhead.

            Integer values displayed in the string could also take up less space than the actual size of the values in the dictionary if they are on average less than 5 digits + whatever the overhead is for values in a dictionary. This is because in Monkey C, simple objects ("primitives", like integers and floats) take exactly 5 bytes afaik: 1 byte for the type field, and 4 bytes for the data.

            If you want to get a good idea of the memory that is taken up by that dictionary, I would create it in a test app (you could just hardcode the dictionary in code) and look at the size in the debugger. I would bet that it's a lot bigger than 12 KB.

            Depending on how you are storing things internally, it might make sense to just pass things as an array, if you run out of space. Or even as a string of comma separated values (or whatever) -- anything that can easily be parsed with a minimum of code on the receiving end. It all depends on how you access that data later.
            Last edited by FlowState; 01-10-2019, 03:39 PM.


            • #7
              FlowState Please, don't apologise, your insights are gold.
              I hear what you say about memory use of Dictionaries, but I wonder if there's a programmatical way of determining the size - observing in the debugger is soo clunky.
              And yes, I may well save memory by casting my data as an array, but in fact, the main space component in this particular dictionary (which I have had to abandon to work around the crash) is a 1000 element array of numbers, so your suggestion may not yield the expected return.
              I still suspect there['s another force at play causing the crash.
              Maybe, as has been suggested on a previous issue, it's a problem with memory allocation in CIQ - unable to find a big enough contiguous space? After all, the error is intermittent!
              raceQs sailboat racing app.


              • #8
                I don't really think I would be able to answer the questions you are asking unless I very familiar with how your application works, and the structure of its active memory. The best I can provide is random speculation about the types of things that could create your symptoms.

                How can you get "Out Of Memory Error" when you a below the memory limit?
                - A large allocation is attempted when your application memory is irrecoverably fragmented.
                The dictionary you are passing is probably quite large if it is 12kb when stringified.
                You should look at how large this dictionary actually is by viewing it in the memory viewer.
                Passing this as a flat structure to save space (Possibly an Array), and unpacking it into multiple objects could reduce fragmentation load.
                - A state change in your application allocates a large amount of memory, and the peak is much lower before it happens

                Why is it random?
                - Fragmentation could behave differently depending on the exact state of your application. A large locked element could make defragging very difficult.
                - A fluke in the exiting application could send larger data than normal, or malformed data that causes an unexpected code path.


                • #9
                  I was just going to mention the memory viewer in the sim, but Brian beat me to it! You can see a whole bunch about the memory used for various things, in # of bytes, etc.

                  When I have something like a large array, I do a "new [MAXSIZE]" and don't grow it. I suspect that doing something like an add(), means memory is obtained for the new size, the old data is copied, and then old memory is then freed, so two large chunks are need at the same time, and if there's fragmentation, that could be an issue, and more so as the app runs and the array keeps getting larger. And it would be random.
                  My Connect IQ Apps in the Store
                  Facebook - Instagram -


                  • #10
                    Thanks all for updating me on ways to assess memory usage of variables.

                    Sure, the Dictionary is big('ish), but it's the same size in all the tests on the different platforms.
                    In all scenarios, the calling app has only been running for a short period of maybe 2 minutes, and the receiving app is crashing immediately: before I can get a System.println out of onStart of the main module.
                    There's not much to be known about the apps: the sending app is building a Dictionary of relevant state variables and exiting to a receiving app which is crashing in onStart().

                    My work around is based on an assumption that the Dictionary size is the issue, but if that turns out to be an incorrect assumption, then I may have a major support issue when the apps are published and deployed with real users.

                    So, my underlying question is why does it crash almost every time on the F5/VAHR devices but very, very rarely on the sim?

                    raceQs sailboat racing app.


                    • #11
                      Sorry, when I said “look at size in debugger”, I meant “look at size in simulator memory viewer” >_>. Jim and Brian beat me to it anyway. And the comments about fragmentation are pretty relevant, especially for large allocations.


                      • #12
                        Originally posted by RaceQs View Post
                        So, my underlying question is why does it crash almost every time on the F5/VAHR devices but very, very rarely on the sim?
                        As much as we try to make the simulator behave exactly like the devices, it is ultimately an impossible task. Even things that essentially function the same will have slight differences in timing, or data behavior. This might mean that your application is responding to data that is presenting differently on the device, or the exact memory usage of your application is a little bit different on each platform.

                        It's always possible there is a bug in the system, but if reducing your memory usage makes an Out of Memory Error go away, it is relatively safe to assume the error is accurate and the application is running out of memory.

                        If you have a reproducible test case in the simulator, the Connect IQ team may be interested in running it to confirm the system is actually failing a memory allocation in an appropriate scenario.


                        • #13
                          Originally posted by FlowState View Post
                          The smallest string in CIQ still takes up 10 bytes or something,
                          I have started to examine the Memory Usage Stats, and indeed find that e null string takes 10 bytes, and a 1-character string takes 11. I Wonder what the 10-byte overheard is?

                          raceQs sailboat racing app.


                          • #14
                            I don't want to give away too many details, but a few of those bytes are the string length and a null terminator. The rest is overhead for the VM.

                            That said, this little bit of test code generates csv data that shows an empty String actually takes up 24 bytes of application memory, and allocations appear to grows in blocks of 8 bytes. I have not looked at why the discrepancy between the memory viewer and this output exists, but it may be something I need to investigate further when I have some free time... or Brian can chime in.

                            using Toybox.Math;
                            using Toybox.Lang;
                            using Toybox.System;
                            (:test) module test {
                                function get_used_memory() {
                                    var systemStats = System.getSystemStats();
                                    if (systemStats != null) {
                                        return systemStats.usedMemory;
                                    return 0;
                                const RANDOM_CHARACTERS =
                                function get_random_bytes(length) {
                                    var s = "";
                                    for (var i = 0; i < length; ++i) {
                                        var j = Math.rand() % RANDOM_CHARACTERS.length();
                                        s += RANDOM_CHARACTERS.substring(j, j+1);
                                    return s;
                                function generate_data_point(logger, count, length) {
                                    var array = new [count];
                                    var before = get_used_memory();
                                    for (var i = 0; i < count; ++i) {
                                        array[i] = get_random_bytes(length);
                                    var after = get_used_memory();
                                    var delta = (after - before);
                                    var average = 1.0 * delta / count;
                                    System.println(Lang.format("$1$, $2$, $3$", [ count.format("%3u"), length.format("%2u"), average ] ));
                                (:test) function string_memory_usage(logger) {
                                    System.println("strings, bytes per string, average size");
                                    for (var i = 0; i < 32; ++i) {
                                        generate_data_point(logger, 100, i);
                                    return true;
                            Generates this...

                            strings, bytes per string, average size
                            100,  0, 24.000000
                            100,  1, 24.000000
                            100,  2, 24.000000
                            100,  3, 24.000000
                            100,  4, 24.000000
                            100,  5, 24.000000
                            100,  6, 24.000000
                            100,  7, 24.000000
                            100,  8, 32.000000
                            100,  9, 32.000000
                            100, 10, 32.000000
                            100, 11, 32.000000
                            100, 12, 32.000000
                            100, 13, 32.000000
                            100, 14, 32.000000
                            100, 15, 32.000000
                            100, 16, 40.000000
                            100, 17, 40.000000
                            100, 18, 40.000000
                            100, 19, 40.000000
                            100, 20, 40.000000
                            100, 21, 40.000000
                            100, 22, 40.000000
                            100, 23, 40.000000
                            100, 24, 48.000000
                            100, 25, 48.000000
                            100, 26, 48.000000
                            100, 27, 48.000000
                            100, 28, 48.000000
                            100, 29, 48.000000
                            100, 30, 48.000000
                            100, 31, 48.000000
                            Last edited by Travis.ConnectIQ; 01-22-2019, 03:40 AM.


                            • #15
                              The difference here is between the size of the memory that was allocated (e.g. 10 bytes) and the space it takes up in the heap after overhead (e.g. 24 bytes).

                              It isn't really something that occurred to me before, but the size in the heap isn't something the memory viewer shows currently. We might want to consider showing the size with overhead in the viewer.

                              The heap block size is currently 8 bytes, which is why you see the size changing in multiples of 8. All the sizes you see in the viewer currently are getting rounded up to the nearest multiple of 8, and then another 8 is added to track the block in the heap. Keep in mind that these details are not guaranteed to stay the same on future versions of the heap though.