5

EDIT

If I use Stopwatch correctly and up the number of iterations by two orders of magnitude I get

Ternary took 22404ms

Normal took 21403ms

These results are closer to what I was expecting and make me feel all is right with the world (if not with my code.)

The Ternary/Conditional operator is in fact marginally slower.


Following on from this question, which I have partially answered.

I compile this console app in x64 Release Mode, with optimizations on, and run it from the command line without a debugger attached.

using System; 
using System.Diagnostics;

class Program
{
    static void Main()
    {
        var stopwatch = new Stopwatch();

        var ternary = Looper(10, Ternary);
        var normal = Looper(10, Normal);

        if (ternary != normal)            {
            throw new Exception();
        }

        stopwatch.Start();
        ternary = Looper(10000000, Ternary);
        stopWatch.Stop();
        Console.WriteLine(
            "Ternary took {0}ms", 
            stopwatch.ElapsedMilliseconds);

        stopwatch.Start();
        normal = Looper(10000000, Normal);
        stopWatch.Stop();
        Console.WriteLine(
            "Normal took {0}ms", 
            stopwatch.ElapsedMilliseconds);

        if (ternary != normal)            {
            throw new Exception();
        }

        Console.ReadKey();
    }

    static int Looper(int iterations, Func<bool, int, int> operation)
    {
        var result = 0;
        for (int i = 0; i < iterations; i++)
        {
            var condition = result % 11 == 4;
            var value = ((i * 11) / 3) % 5;
            result = operation(condition, value);
        }

        return result;
    }

    static int Ternary(bool condition, in value)
    {
        return value + (condition ? 2 : 1);
    }

    static int Normal(int iterations)
    {
        if (condition)
        {
            return = 2 + value;
        }
        
        return = 1 + value;
    }
}

I don't get any exceptions and the output to the console is somthing close to,

Ternary took 107ms

Normal took 230ms

When I break down the CIL for the two logical functions I get this,

... Ternary ...
{
     : ldarg.1      // push second arg
     : ldarg.0      // push first arg
     : brtrue.s T   // if first arg is true jump to T
     : ldc.i4.1     // push int32(1)
     : br.s F       // jump to F
    T: ldc.i4.2     // push int32(2)
    F: add          // add either 1 or 2 to second arg
     : ret          // return result
}

... Normal ...
{
     : ldarg.0      // push first arg
     : brfalse.s F  // if first arg is false jump to F
     : ldc.i4.2     // push int32(2)
     : ldarg.1      // push second arg
     : add          // add second arg to 2
     : ret          // return result
    F: ldc.i4.1     // push int32(1)
     : ldarg.1      // push second arg
     : add          // add second arg to 1
     : ret          // return result
}

Whilst the Ternary CIL is a little shorter, it seems to me that the execution path through the CIL for either function takes 3 loads and 1 or 2 jumps and a return. Why does the Ternary function appear to be twice as fast.

I underdtand that, in practice, they are both very quick and indeed, quich enough but, I would like to understand the discrepancy.

Community
  • 1
  • 1
Jodrell
  • 34,946
  • 5
  • 87
  • 124
  • 3
    You don't reset the stopwatch between runs. I've never used that class personally, but the docs indicate that the `Start()` method will *resume* timing. I don't think the discrepancy is as great as you think. Try switching the second `Start()` call to `Restart()`. – Roddy of the Frozen Peas Sep 17 '12 at 15:30
  • You're also measuring the time to call each of the `Func`s? How does that come into play here? – Chris O Sep 17 '12 at 15:31
  • @RoddyoftheFrozenPeas, you are correct. – Jodrell Sep 17 '12 at 15:32
  • 1
    Looking at the others' answers: To answer my own questions, it doesn't matter about the different methods. – Chris O Sep 17 '12 at 15:33

1 Answers1

8

The two take pretty much exactly the same amount of time.

Your results are off because you simply didn’t use Stopwatch correctly. The measurement for “Normal” includes the time taken by both loopers.

If you change the second

stopwatch.Start();

to

stopwatch.Restart();

Then you will get the correct results.


By the way, to get a fairer comparison, you should probably execute

    return (condition ? value + 2 : value + 1);

instead of

    return value + (condition ? 2 : 1);

so that it is exactly equivalent to the other function. Otherwise you’re measuring not only the conditional operator.

Timwi
  • 65,159
  • 33
  • 165
  • 230
  • We could however still do with understanding how this was measured in the original question... – Robbie Dee Sep 17 '12 at 15:46
  • @HenkHolterman Having a consistent way of benchmarking the code is a must. Without this, how can you possibly hope to optimise? – Robbie Dee Sep 17 '12 at 21:28
  • @RobbieDee - nobody is saying it is easy, useful benchmarking is very hard. – H H Sep 17 '12 at 21:37