switch vs if

I am interested to hear opinions about what the preferred way would be to implement the following logic and why:

switch
if ( dateFmt < 4 ) {
switch ( dateFmt ) {
case 0: fmt = "$1$ $2$ $3$"; break;
case 1: fmt = "$1$, $2$ $3$"; break;
case 2: fmt = "$1$ $2$ $3$."; break;
}
}


if
if ( dateFmt < 4 ) {
if (dateFmt == 0) {fmt = "$1$ $2$ $3$";}
else if (dateFmt == 1) {fmt = "$1$, $2$ $3$";}
else if (dateFmt == 2) {fmt = "$1$ $2$ $3$.";}
}


Performance and memory wise, which one is better?
  • Fortunately, I think you're leaving out a very important third option; implement a jump table yourself.

    const _date_formats = [
    // insert three format strings here. I can't post code because of the forum bug...
    ];

    if <[dateFmt < 4]> {
    fmt = $._date_formats[dateFmt];
    }


    This disassembles to the following one-time initialization code...

    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_129_0:
    BUILD: lgetv 0
    BUILD: spush _date_formats
    BUILD: ipush 3
    BUILD: newa
    BUILD: dup 0
    BUILD: ipush 0
    BUILD: news @str_1___2___3__1337593282
    BUILD: aputv
    BUILD: dup 0
    BUILD: ipush 1
    BUILD: news @str_1____2___3__784014432
    BUILD: aputv
    BUILD: dup 0
    BUILD: ipush 2
    BUILD: news @str_1___2___3___1484281172
    BUILD: aputv
    BUILD: putv
    BUILD: return


    And then this is the generated output to fetch the date format from the global array...

    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_140_8:
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_12:
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_12_if:
    BUILD: lgetv 0
    BUILD: spush dateFmt
    BUILD: getv
    BUILD: ipush 4
    BUILD: lt
    BUILD: bf @Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_12_else
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_31_start:
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_143_16:
    BUILD: spush globals
    BUILD: getm
    BUILD: spush _date_formats
    BUILD: getv
    BUILD: lgetv 0
    BUILD: spush dateFmt
    BUILD: getv
    BUILD: agetv
    BUILD: lputv 1
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_31_stop:
    BUILD: goto @Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_12_end
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_12_else:
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_142_12_end:
    BUILD: Q_eclipse_unstable_workspace_ZZZ_source_ZZZApp_mc_137_26_stop:


    That is 17 instructions and 1 label for initialization, and 16 instructions and 8 labels for fetch. If you ignore the one-time initialization cost, that is 72% fewer instructions than the switch, and 59% fewer instructions than the if. If you count the one-time init, those numbers fall to 42% and 15% respectively.

    Of course, that is just the raw instruction count. If you want to figure out performance implications you'd have to figure out the relative costs of each instruction and then evaluate the min/max/avg cost for each of those bits of code or write a performance test.

    The other thing to consider is that the array trick uses up 4 objects from the total number of objects available to you. This is a trade-off that you'd have to decide based on your app and the resources available to you.
  • Note also that the outer if in your code allows dateFmt == 3 which isn't handled by the if or switch. You don't really need that check for the if or switch for the case that you've provided, but it would be safest to use that check when doing the manual lookup table as I proposed.

    Travis
  • Wow Travis! Thanks so much for the very detailed answers and feedback!
    That gives a very clear indication of the final compiled code.

    Sorry for not having supplied the correct samples or giving better context as to what I was looking for, but I surely got the answers from you.
    I was not really trying to solve some issue on my side, I was just curious about the performance and memory impacts of each approach.
  • Travis, I really like your suggestion for the code improvement by writing my own jump table. This together with all the figures give me a new perspective on how to optimize things.

    Regarding the one-time initialization cost:
    For a watch face I try and have as small initialization cost as possible. I try to avoid has-checks and loading too many things during that time.
    The reason for that being that it gives a user a much smoother transitioning experience in the widget loop when returning to a watch face.

    But I realize that the current implementation comes at the expense of wasted cycles and therefor a negative impact on the battery life.

    Like you said, there is a lot of tradeoffs and one has to decide which is best based on your own priorities.
    For me it is a constant juggle between battery consumption and smoother loading experience.
  • I haven't gone as far as travis to compile with the -g switch, but if you have just 2 options - just if and else branch I'm having the impression that using the ? operator induces less memory use... especially when using in combination with assignments.

    eg
    var tdt = app.getProperty("targetDist") == null? 1.0 : app.getProperty("targetDist");
  • Absolutely. This is really no different than a simple if/else.

    If you are actually calling getProperty() like you've posted above, and you're concerned about wasted instructions or performance, you should consider making a small change. The code you've posted will call getProperty() twice when the targetDist property has a non-null value. This is unnecessarily expensive. This small change will avoid that entirely.

    var tdt = app.getProperty("targetDist");
    if (tdt == null) {
    tdt = 1.0;
    }


    On a related note, I posted the source for a Settings class a long time ago. That class wraps common usage patterns like this (default values for properties) as well as fixes for some known issues (reading properties that should be floats that come back as strings). Using a wrapper like that does add some overhead (it increases the size of the program and reduces the amount of available memory slightly), but it does have the advantage that you never need to think about these details.