Search code examples
if-statementmathhaxe

Haxe: Efficiency of mapping a variable to one of two values


I feel this question is too general and poorly asked. Improvement suggestions would be appreciated.

Say I have an integer variable which I need to convert to one of two other values. If the variable is 0, it will remain 0, otherwise, it will become 1. I can think of two ways to do this.

Method 1: An inline if/else assignment.

function runFunc(input:Int):Void
{
    <script>
}

for (index in 0...5)
{
    runFunc(if (index == 0) {0;} else {1;});
}

Method 2: Division and rounding.

function runFunc(input:Int):Void
{
    <script>
}

for (index in 0...5)
{
    runFunc(Math.round(index / index));
}

Method 1 is more standardised and would work for other data-types and other values, but Method 2 seems like it would take less processing power (especially if this needs to be done almost constantly).

Assuming that Method 2 wouldn't have a problem with rounding, is there enough of a difference in times between the two methods to consider using one over the other? How do different things like if/else statements and Math.round() impact processing time?


Solution

  • Well, first things first, your method 2 has a division by zero. So it's not even a valid solution.

    However, I assume you want an answer "in general". And, of course, this kind of question comes with a TON of "it depends" qualifications. It depends on the type of CPU, the programming language, the optimizations the compiler might make, runtime optimizations, etc, etc.

    However, in general, the order of cost of the relevant operations is roughly:

    1. Logic and arithmetic operations are cheapest
      • Of these, integer operations are cheaper than floating point operations.
    2. Branches (ifs and loops) are moderately expensive.
    3. Division is moderately expensive.
    4. Function calls are very expensive.

    Basically, this is because (respectively):

    1. This is what CPUs do, and they are optimized to do it quickly
    2. Branches represent two possible paths, which can slow down CPU parallelization and optimizations.
    3. Division is a multi-step process.
    4. Function calls have to push and pop the stack.

    Thus, I'd probably bet on your method 1 above. And, just for clarity, I'd write the if statement in your first example with an equivalent ternary operator:

    for (index in 0...5)
    {
        runFunc( index==0 ? 0 : 1 );
    }
    

    This is well and good for mapping a simple boolean (either index is 0 or it isn't). But what about when the mapping becomes more complex? There are a couple constructs that generally give good performance when mapping one value to another: a lookup table or a hash map.

    The lookup table is useful if your keys are integers, bounded between some minimum and maximum values. The hash map is good if your keys are hashable (often Ints, Strings, or Objects can be used as hash keys. Look at haxe.ds.IntMap, haxe.ds.StringMap and haxe.ds.ObjectMap.)

    Example 1: A common look-up table might map numeric integer "day of the week" to the word used for that day. Assuming day:Int is always 0-6, a day int to string lookup table would be:

    var day_name_lut = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" ];
    

    So now trace('Today is a '+day_name_lut[Date.now().getDay()]); will tell us what day it is today. That's mapping an integer to an String for VERY low cost. Cheaper than a function call or a IntMap<String>.

    Example 2: A sample StringMap might be used to store some object by a string value (for example, maybe a Person by their name.) This will allow us to lookup people by name later on:

    var people_by_name = new StringMap<Person>();
    var joe = new Person("Joe");
    people_by_name.set(joe.name, joe);
    var bob = new Person("Bob");
    people_by_name.set(bob.name, bob);
    

    This is very common for caching expensive oeprations. Imagine caching an HTTP response by its URL. It'd be much cheaper to lookup the second time from the StringMap than going back to fetch the response again.

    --- update ---

    I also note you say "inline if/else assignment", referring to this:

    runFunc(if (index == 0) {0;} else {1;});
    

    Note that actually writing the if inline is the kind of thing that makes absolutely no performance difference. It'll perform exactly the same as:

    var tmp = if (index == 0) {0;} else {1;}
    runFunc(tmp);
    

    And these are exactly the same as well:

    runFunc( if (index == 0) 0 else 1);
    runFunc( (index == 0) ? 0 : 1);
    

    These types of semantic differences don't make any difference to the final execution of code. The processor still has to compute and temporarily store the value to call the function, whether you wrote it inline, stored it in a tmp variable, or wrote the branch with an if/else or ternary operator. Compilers and VMs are optimized to take your (potentially variable) code and boil it down to the best instructions for the target CPU.