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.
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.