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.
It is not faster. There is one difference when you can initialize a constant variable depending on some expression: const int x = (a<b) ?
Yes! The second is vastly more readable.
There's a different emphasis: An if / else statement emphasises the branching first and what's to be done is secondary, while a ternary operator emphasises what's to be done over the selection of the values to do it with.
There should not be a major difference in speed. However, the switch is far more readable and maintainable. The switch option is easier to debug.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With