Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 234 Vote(s) - 3.53 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Ternary operator is twice as slow as an if-else block?

#1
I read everywhere that ternary operator is supposed to be faster than, or at least the same as, its equivalent `if`-`else` block.

However, I did the following test and found out it's not the case:

Random r = new Random();
int[] array = new int[20000000];
for(int i = 0; i < array.Length; i++)
{
array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);

long value = 0;
DateTime begin = DateTime.UtcNow;

foreach (int i in array)
{
if (i > 0)
{
value += 2;
}
else
{
value += 3;
}
// if-else block above takes on average 85 ms

// OR I can use a ternary operator:
// value += i > 0 ? 2 : 3; // takes 157 ms
}
DateTime end = DateTime.UtcNow;
MessageBox.Show("Measured time: " + (end-begin).TotalMilliseconds + " ms.\r\nResult = " + value.ToString());

My computer took 85 ms to run the code above. But if I comment out the `if`-`else` chunk, and uncomment the ternary operator line, it will take about 157 ms.

Why is this happening?
Reply

#2
Run without debugging ctrl+F5 it seems the debugger slows down both ifs and ternary significantly but it seems it slows down the ternary operator much more.

When I run the following code here are my results. I think the small millisecond difference is caused by the compiler optimizing the max=max and removing it but is probably not making that optimization for the ternary operator. If someone could check the assembly and confirm this it would be awesome.

--Run #1--
Type | Milliseconds
Ternary 706
If 704
%: .9972
--Run #2--
Type | Milliseconds
Ternary 707
If 704
%: .9958
--Run #3--
Type | Milliseconds
Ternary 706
If 704
%: .9972

Code

for (int t = 1; t != 10; t++)
{
var s = new System.Diagnostics.Stopwatch();
var r = new Random(123456789); //r
int[] randomSet = new int[1000]; //a
for (int i = 0; i < 1000; i++) //n
randomSet[i] = r.Next(); //dom
long _ternary = 0; //store
long _if = 0; //time
int max = 0; //result
s.Start();
for (int q = 0; q < 1000000; q++)
{
for (int i = 0; i < 1000; i++)
max = max > randomSet[i] ? max : randomSet[i];
}
s.Stop();
_ternary = s.ElapsedMilliseconds;
max = 0;
s = new System.Diagnostics.Stopwatch();
s.Start();
for (int q = 0; q < 1000000; q++)
{
for (int i = 0; i < 1000; i++)
if (max > randomSet[i])
max = max; // I think the compiler may remove this but not for the ternary causing the speed difference.
else
max = randomSet[i];
}

s.Stop();
_if = s.ElapsedMilliseconds;
Console.WriteLine("--Run #" + t+"--");
Console.WriteLine("Type | Milliseconds\nTernary {0}\nIf {1}\n%: {2}", _ternary, _if,((decimal)_if/(decimal)_ternary).ToString("#.####"));
}

Reply

#3
EDIT: All change... see below.

I can't reproduce your results on the x64 CLR, but I *can* on x86. On x64 I can see a *small* difference (less than 10%) between the conditional operator and the if/else, but it's much smaller than you're seeing.

I've made the following potential changes:

- Run in a console app
- Build with `/o+ /debug-`, and run outside the debugger
- Run both pieces of code once to JIT them, then lots of times for more accuracy
- Use `Stopwatch`

Results with `/platform:x64` (without the "ignore" lines):

if/else with 1 iterations: 17ms
conditional with 1 iterations: 19ms
if/else with 1000 iterations: 17875ms
conditional with 1000 iterations: 19089ms

Results with `/platform:x86` (without the "ignore" lines):

if/else with 1 iterations: 18ms
conditional with 1 iterations: 49ms
if/else with 1000 iterations: 17901ms
conditional with 1000 iterations: 47710ms

My system details:

- x64 i7-2720QM CPU @2.20GHz
- 64-bit Windows 8
- .NET 4.5

So unlike before, I think you *are* seeing a real difference - and it's all to do with the x86 JIT. I wouldn't like to say exactly *what* is causing the difference - I may update the post later on with more details if I can bother to go into cordbg :)

Interestingly, without sorting the array first, I end up with tests which take about 4.5x as long, at least on x64. My guess is that this is to do with branch prediction.

Code:

using System;
using System.Diagnostics;

class Test
{
static void Main()
{
Random r = new Random(0);
int[] array = new int[20000000];
for(int i = 0; i < array.Length; i++)
{
array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);
// JIT everything...
RunIfElse(array, 1);
RunConditional(array, 1);
// Now really time it
RunIfElse(array, 1000);
RunConditional(array, 1000);
}

static void RunIfElse(int[] array, int iterations)
{
long value = 0;
Stopwatch sw = Stopwatch.StartNew();

for (int x = 0; x < iterations; x++)
{
foreach (int i in array)
{
if (i > 0)
{
value += 2;
}
else
{
value += 3;
}
}
}
sw.Stop();
Console.WriteLine("if/else with {0} iterations: {1}ms",
iterations,
sw.ElapsedMilliseconds);
// Just to avoid optimizing everything away
Console.WriteLine("Value (ignore): {0}", value);
}

static void RunConditional(int[] array, int iterations)
{
long value = 0;
Stopwatch sw = Stopwatch.StartNew();

for (int x = 0; x < iterations; x++)
{
foreach (int i in array)
{
value += i > 0 ? 2 : 3;
}
}
sw.Stop();
Console.WriteLine("conditional with {0} iterations: {1}ms",
iterations,
sw.ElapsedMilliseconds);
// Just to avoid optimizing everything away
Console.WriteLine("Value (ignore): {0}", value);
}
}
Reply

#4
I did what Jon Skeet did and ran through 1 iteration and 1,000 iterations and got a different result from both OP and Jon. In mine, the ternary is just slightly faster. Below is the exact code:

static void runIfElse(int[] array, int iterations)
{
long value = 0;
Stopwatch ifElse = new Stopwatch();
ifElse.Start();
for (int c = 0; c < iterations; c++)
{
foreach (int i in array)
{
if (i > 0)
{
value += 2;
}
else
{
value += 3;
}
}
}
ifElse.Stop();
Console.WriteLine(String.Format("Elapsed time for If-Else: {0}", ifElse.Elapsed));
}

static void runTernary(int[] array, int iterations)
{
long value = 0;
Stopwatch ternary = new Stopwatch();
ternary.Start();
for (int c = 0; c < iterations; c++)
{
foreach (int i in array)
{
value += i > 0 ? 2 : 3;
}
}
ternary.Stop();


Console.WriteLine(String.Format("Elapsed time for Ternary: {0}", ternary.Elapsed));
}

static void Main(string[] args)
{
Random r = new Random();
int[] array = new int[20000000];
for (int i = 0; i < array.Length; i++)
{
array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);

long value = 0;

runIfElse(array, 1);
runTernary(array, 1);
runIfElse(array, 1000);
runTernary(array, 1000);

Console.ReadLine();
}


The output from my program:
>Elapsed time for If-Else: 00:00:00.0140543
>
>Elapsed time for Ternary: 00:00:00.0136723
>
>Elapsed time for If-Else: 00:00:14.0167870
>
>Elapsed time for Ternary: 00:00:13.9418520

Another run in milliseconds:

>Elapsed time for If-Else: 20
>
>Elapsed time for Ternary: 19
>
>Elapsed time for If-Else: 13854
>
>Elapsed time for Ternary: 13610

This is running in 64-bit XP, and I ran without debugging.

**Edit - Running in x86:**

There's a big difference using x86. This was done without debugging on and on the same xp 64-bit machine as before, but built for x86 CPUs. This looks more like OP's.

>Elapsed time for If-Else: 18
>
>Elapsed time for Ternary: 35
>
>Elapsed time for If-Else: 20512
>
>Elapsed time for Ternary: 32673
Reply

#5
The assembler code generated will tell the story:

a = (b > c) ? 1 : 0;

Generates:

mov edx, DWORD PTR a[rip]
mov eax, DWORD PTR b[rip]
cmp edx, eax
setg al

Whereas:

if (a > b) printf("a");
else printf("b");

Generates:

mov edx, DWORD PTR a[rip]
mov eax, DWORD PTR b[rip]
cmp edx, eax
jle .L4
;printf a
jmp .L5
.L4:
;printf b
.L5:

So the ternary _can_ be shorter and faster simply due to using fewer instructions and no jumps _if_ you are looking for true/false. If you use values other than 1 and 0, you will get the same code as an if/else, for example:

a = (b > c) ? 2 : 3;

Generates:

mov edx, DWORD PTR b[rip]
mov eax, DWORD PTR c[rip]
cmp edx, eax
jle .L6
mov eax, 2
jmp .L7
.L6:
mov eax, 3
.L7:

Which is the same as the if/else.
Reply

#6
Looking at the IL generated, there are 16 less operations in that than in the if/else statement (copying and pasting @JonSkeet's code). However, that doesn't mean it should be a quicker process!

To summarise the differences in IL, the if/else method translates to pretty much the same as the C# code reads (performing the addition within the branch) whereas the conditional code loads either 2 or 3 onto the stack (depending on the value) and then adds it to value outside of the conditional.

The other difference is the branching instruction used. The if/else method uses a brtrue (branch if true) to jump over the first condition, and an unconditional branch to jump from the first out of the if statement. The conditional code uses a bgt (branch if greater than) instead of a brtrue, which could possibly be a slower comparison.

Also (having just read about branch prediction) there may be a performance penalty for the branch being smaller. The conditional branch only has 1 instruction within the branch but the if/else has 7. This would also explain why there's a difference between using long and int, because changing to an int reduces the number of instructions in the if/else branches by 1 (making the read-ahead less)
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through