Acknowledged

ByteArray addAll() not Memory Efficient

When using the addAll() method of Lang.ByteArray, peak memory usage implies that the ConnectIQ framework is doubling the size of the array being appended to instead of extending it by the size of the appended array.

For example, let's say we create a 1K byteArray called myArray and run the following code starting with an empty byte array, longByteArray:

while(longByteArray.size() < 32768) {
    longByteArray.addAll(myArray);
}

An efficient implementation would grow peak memory usage by ~1K more than longByteArray.size(). Instead, we see peak memory usage growing with longByteArray.size() * 2 + 1024.

It may also be worth noting that the Monkey C extension for Visual Studio code will time out as the array grows if a developer sets a breakpoint on the addAll function above while the watch window is open to longByteArray.

  • It's not allocating temporary memory:

    this is the memory before you all addAll:

    ---b----n----[longByteArray]---[myArray]---x---y------------------------------------------------------------------------------------------------------ 

    and this is just after it copied the last byte of myArray for the 1st time:

    ---b----n----[longByteArray]---[myArray]---x---y-[longByteArraymyArray]---------------------------------------------------------------------- 

    this is when it freed the old longByteArray:

    ---b----n----------------------------[myArray]---x---y-[longByteArraymyArray]---------------------------------------------------------------------- 

    this is after it added myArray for the 2nd time:

    ---b----n----------------------------[myArray]---x---y-[longByteArraymyArray]-[longByteArraymyArraymyArray]--------------------------

    etc...

    at the end of the last time it adds myArray (depending on how many times it freed the old longByteArray and whether there's enough continous space "before" or "after" the actual "old" longByteArray) it could be something like:

    -b-n----------[myArray]-x-y-[longByteArraymyArraymyArraymyArraymyArray]---------------[longByteArraymyArraymyArraymyArray]-----

    or worse:

    -b-n----------[myArray]-x-y----------------------------------------------------------[longByteArraymyArraymyArraymyArray]-[longByteArraymyArraymyArraymyArraymyArray]

    But from both of these you can see that the minimum amount of memory it needs is:

    1* myArray (the original)

    1* original longByteArray + (n-1) * myArray (the one it adds the last time myArray to)

    1* original longByteArray + (n) * myArray (the result)

    SUM: 2 * originalLongByteArray + (1 + (n - 1) + n) * myArray = 2 * originalLongByteArray + 2n * myArray

    so if the origial longArray was empty then this is exactly what you saw

  • I feel like the native CIQ addAll() function could detect that there is enough memory preallocated in the target array to add bytes without allocating temporary memory.

    Correct, I mean the watchdog. I understand that it's possible to split the operation to avoid a watchdog timeout, but this seems like it would represent a workaround on top of a workaround for addAll(). Using a loop to add the bytes is also a lot less time efficient than addAll() since it has the advantage of native code.

  • Memory has always been tight in CIQ,  I can't see a way this could change.

    By "timeout", you mean the watchdog?  That's how long something can run without returning to the VM,  You can work around this by using a time and do part of the copy during each timer tick.

  • I agree with you. My intent is to suggest an improvement to the CIQ framework so that it no longer doubles the memory. It would be fine if CIQ required a preallocated byteArray in that case.

    I also think there is an opportunity to improve the CIQ Monkey C plug-in so it doesn't timeout on a large byteArray.

  • add() and addAll() double the memory (addAll() can be more than double).  There's no way to avoid that if you use them.  It's something you need to consider in your design.