Tips for programming good performance

Hi,

I designed a datafield which shows a lot of information. Some of that information needs to be calculated (like lap pace, 60 sec pace, 10 sec pace, etc). Apart from this I have some string format calculations and some (maybe a lot) drawings to do. During my last run I realized that:
a) the datafield's graphics updates only every 2nd second
b) the last lap time (I visualize the current lap time and the last lap time via the datafield) is sometimes 3 seconds behind that lap time the watch calculates for its own (and sometimes not - i. e. I loose some +1 events)
c) the lap pace jumps around up to plus/minus 10-15 seconds during the lap (which is not if you simulate the run afterwards in the simulator)

I checked my code and realized e. g. that I converted the elapsed time from ms to seconds in every pace calculation, in fact I did that 4 times. OK, no problem, I refactored the code here. Furthermore I handled the calculation of lap time in two different threads seperately. I have to refactor the code to calculate and use the lap time just once.

After this obvious optimizations there are several more left which I want to discuss here:
1. The algorithms itself to calculate the paces
2. The math algorithm to ease the micro processor's life
3. The drawings

To 2.:
For the 60 seconds pace I have written this:
sixtySecondsSpeed = ((sixtySecondsSpeed * 59) + info.currentSpeed ) / 60;


Maybe it is more effective for the micro processor to code something like this:
sixtySecondsSpeed = (sixtySecondsSpeed * 64) - sixtySecondsSpeed;
sixtySecondsSpeed = (sixtySecondsSpeed + info.currentSpeed) /64;

IMO the second implementation could be much more efficient since the multiplication and division is just a bit shift for the micro processor. The disadvantage is that the average is now a 64 seconds average, but this is negligible.

Maybe the same is valid for comparison of counting values: I have e. g. this code:
if (LapTime < 20) { do something } else { do other stuff }

Maybe it is more efficient to code this:
if (Laptime = 0) { LapTimeSmallerThanTwenty = true; }
if (Laptime = 20) { LapTimeSmallerThanTwenty = false; }
if (LapTimeSmallerThanTwenty) { do something } else { do other stuff }


What do you think?



To 3.:
I have to do all of the drawings again and again in the onUpdate() method. IMO this is not efficient since a lot of parts of the datafield are static (like lines and labels). Therefore I tried to use a layout but the datafield gets very fast out of memory if I define all the datafield's lines and labels in the XML file. Therefore I am seeking for an option which is more efficient than drawing the same every second. Any hints on that?


To 1.:
Algorithm to get to the mean values. I showed in (2.) above how I calculate the mean values of the 10 seconds and 60 seconds pace. Nevertheless, maybe there is a better and more efficient way to calculate the 10 seconds pace. Apart from the algorithm itself I could use a 8 seconds or a 16 seconds pace and would ease the compiler's resources as I showed in (2.). But I consider to update the entire algorithm:
1. I define an array with 10 elements and use it as a FIFO
2. I write the following (pseudo) code (note that tenSecondsSpeedSum shall be the sum of all array values):
tenSecondsSpeedSum = tenSecondsSpeedSum - tenSecondsSpeedArray[0] + info.currentSpeed;
tenSecondsSpeedArray[0:8] = tenSecondsSpeedArray[1:9];
tenSecondsSpeedArray[9] = info.currentSpeed;
tenSecondsSpeed = tenSecondsSpeedSum / 10;


By doing this I have the following advantages:
  • I get rid of the multiplication
  • I get a real 10 seconds average

but I get following disadvantages:
  • I get an additional minus operation
  • I need to define an array with 10 elements which needs memory


What do you think?
  • Former Member
    Former Member
    No, you are missing nothing. IMO this is an easy way to save valuable memory space (by not defining an array of 60 values) and is called in german (I haven't found an english translation): PT1-Filter: Accu = Value * Weight + Accu * (1.0-Weight);

    sixtySecondsSpeed = ((sixtySecondsSpeed * 59) + info.currentSpeed ) / 60;
    This is pretty cool but only works well if the value dropping off the end is close to the moving average.
  • Hi again,

    thanks for all your tips. They are very helpful. I will rewrite the algorthims during the upcoming weekend.

    I have a further question/discussion to calculating the lap pace concerning accuracy but also to performance:
    1. Accuracy

    The new SDK introduced the onLapTimer() method. Unfortunately, we have access to the info object only in the compute method. Is it just fine to create a link to the info class by doing this?


    using Toybox.Activity.Info as ActInfo;

    function onLapTimer () {
    previousLapTime = ActInfo.elapsedTime;
    previousLapDistance = ActInfo.elapsedDistance;
    }


    Otherwise I have to decrease accuracy by doing this:


    function onLapTimer() {
    newLapDetected = true;
    }

    function compute(info) {
    if (newLapDetected) {
    newLapDetected = false;
    previousLapTime = info.elapsedTime;
    previousLapDistance = info.elapsedDistance;
    }
    }


    The second implementation is ok, but not as accurate as the first one.


    2. Performance
    I am considering an algorithm which is performant. I could do it like this every second (in the compute(info) method):

    function compute(info) {
    lapTime = info.elapsedTime - previousLapTime;
    speed = (info.elapsedDistance - previousLapDistance) / lapTime;
    }


    Nevertheless, there are two minus operations and one division to do. E. g. do you think, it is more efficient to define a lapTime variable which is set to 0 at onTimerLap and do an +1 operation in the compute(info) method?
    Do you have any further suggestions for an alternative algorithm?

    Edit: Maybe a better performance can be achieved for my special datafield: I show the 60 Seconds pace and the lap pace and could do this to get a better performance:

    function compute(info) {
    if (lapTime && 1 == 0) { // assumption: lapTime in seconds
    // do the lap Pace calculation
    } else {
    // do the 60 seconds Pace calculation
    }
    }


    This means, I would update each pace information every 2nd second.
  • Unfortunately, we have access to the info object only in the compute method.

    You can get access to the activity info any time by calling Activity.getActivityInfo().

    there are two minus operations and one division to do. E. g. do you think, it is more efficient to define a lapTime variable which is set to 0 at onTimerLap and do an +1 operation in the compute(info) method?

    You are far too focused on optimization. Write code that works first. Then, if you find it is necessary, optimize that code.

    Just consider the two code snippets you're suggesting...

    function compute(info) {
    lapTime = info.elapsedTime - previousLapTime;
    speed = (info.elapsedDistance - previousLapDistance) / lapTime;
    }


    vs.

    function compute(info) {
    lapTime = lapTime + 1;
    speed = (info.elapsedDistance - previousLapDistance) / lapTime;
    }


    The first line is the only difference. In the first case it is two loads, a subtract, a store. In the second case it is two loads, an add, and a store. I'm not seeing that one is computationally less expensive than the other, and given that this code only needs to execute once a second, it just doesn't matter.

    function compute(info) {
    if (lapTime && 1 == 0) { // assumption: lapTime in seconds
    // do the lap Pace calculation
    } else {
    // do the 60 seconds Pace calculation
    }
    }

    This code is wrong twice. The expression lapTime && 1 == 0 will be evaluated as lapTime && (1 == 0), which will always evaluate to false (operator == has higher precedence than &&). Even if you fix the precedence problem, by switching to (lapTime && 1) == 0, the code would still be wrong as it would evaluate to true only if lapTime is 0. You want to use bitwise and (&)...

    if (info.elapsedTime & 1) {
    }


    Travis
  • Hi Travis,

    thanks for all your suggestions. Now my code is much more efficient and apart from this the accuracy increased.
  • Former Member
    Former Member
    Although my problem is only partly related to performance, I decided to post here, since this is the only thread, where bit shift was mentioned.

    In my current application I try to do everything in the most memory efficient way as my app receives configuration setting of variable length and allocated array based on that config, therefore the more memory is free the better.
    One of my memory improvement was related to units conversion settings. I've already had people complaining about how they use all three units related settings in different combinations so it's necessary to store all three of them to check conversions after onCompute. As each of these settings is either METRIC or STATUTE which are either 0 or 1, I decided to go for one Number which will store all three values in different bits:

    tV = Sys.Sys.getDeviceSettings();
    conv = tV.elevationUnits << 2 | tV.distanceUnits << 1 | tV.paceUnits;


    This worked fine in simulator but when I tried this app on real watch it crashed with an "Unhandled Exception". After long research I found out, that this crash happens because of the line above, which left shifts and combines units conversion flags. When I replaced the above line with

    conv = (tV.elevationUnits ? 4 : 0) | (tV.distanceUnits ? 2 : 0) | tV.paceUnits;

    which is actually the same in my opinion, app started to work without any problems. My guess is that problem appears only if I try to left shift zero values since all of my units settings on watch are METRIC which is equal to zero according to constant definitions, but in simulator it works with any value. Question is: why does this expression fails on the device? Currently I'm good with the workaround from the second code block, but I'm still interested whether I wrote something wrong in the first place. Any thoughts are welcome. Thanks in advance.
  • Kind of a general question. Why save these at all? You can just check Sys.getDeviceSettings().xxxUnits when needed. In the case of a data field, the user can actually change a unit from Statute to Metric while the DF is running, so you'll have to keep checking and redoing your saved version in onLayout() or onUpdate().

    Generally, in compute() I do everything in Metric as that's how the data is in Activity.Info, and do the conversion in onUpdate() when the data is displayed, and if the units change, no problem...

    As far as the shift, that looks like it should work to me, but what device/FW and SDK version are you using as there could be something odd there. I seem to recall, that something like one of the units perhaps, was seen as true and false instead of 1 and 0 at some point, so that could be what's happening too
  • Former Member
    Former Member
    jim_m_58
    ...Why save these at all? You can just check Sys.getDeviceSettings().xxxUnits when needed. In the case of a data field, the user can actually change a unit from Statute to Metric while the DF is running...

    Didn't know that user can change units when activity is running, thank you for letting me know, although I can't imagine that many people actually do that even if they can, doesn't sound very logical to me.
    As I already mentioned, my app is very intensive in it's main cycle: currently with one heavy layout definition I can run app without any problems for more than two hours while there's 14.6kb current vs 15.8kb maximum memory consumption according to simulator, I recon this 1kb difference is due to the amount and type of interim variables, which I use every compute/update tick. Currently I read and assign these settings in view initialize section, but if I move it within compute I almost immediately get out of memory crash, which means, that DeviceSettings object is rather large and I think that I'll stay with getting settings in initialization phase for now.

    But the original question persists: is it normal that left shift zero value crashes the app on device or is this a "feature"?
  • Former Member
    Former Member
    Is it possible one of your values is ending up null? I am not sure if "null ? 1 : 0" is a valid statement, but "null << 1" is definitely invalid.

    If that is not what is happening, then there is probably a device bug we want to investigate. If you can confirm this isn't an issue with null, I think you should create an official bug report in the bug forum.
  • I filed a bug on this a while back. The problem is that deviceSettings.elevationUnits (and its ilk) are not of type Lang.Number as one would expect, they are of type Lang.Boolean. What version of the SDK are you using, and what device firmware (ConnectIQ version) are you using? As far as I can tell, the bug has been fixed in the simulator and the devices I've tested with, but the problem could still exist on some devices on some firmware versions.

    Travis