Speed Difference Between If-Else and Ternary Operator in C...

Speed difference between If-Else and Ternary operator in C...?

There's a good chance that the ternary operator gets compiled into a cmov while the if/else results in a cmp+jmp. Just take a look at the assembly (using -S) to be sure. With optimizations enabled, it won't matter any more anyway, as any good compiler should produce the same code in both cases.

Ternary operator ?: vs if...else

Depends on your compiler, but on any modern compiler there is generally no difference. It's something you shouldn't worry about. Concentrate on the maintainability of your code.

Performance of ternary operator vs if-else statement

I doubt there is a performance difference. They compile to equivalent sequences of bytecodes:

>>> def f():
... return a if b else c
...
>>> dis.dis(f)
2 0 LOAD_GLOBAL 0 (b)
2 POP_JUMP_IF_FALSE 8
4 LOAD_GLOBAL 1 (a)
6 RETURN_VALUE
>> 8 LOAD_GLOBAL 2 (c)
10 RETURN_VALUE
>>> def g():
... if b:
... return a
... else:
... return c
...
>>> dis.dis(g)
2 0 LOAD_GLOBAL 0 (b)
2 POP_JUMP_IF_FALSE 8

3 4 LOAD_GLOBAL 1 (a)
6 RETURN_VALUE

5 >> 8 LOAD_GLOBAL 2 (c)
10 RETURN_VALUE
12 LOAD_CONST 0 (None)
14 RETURN_VALUE

As with most performance questions, the answer is to measure.

Ternary ? operator vs the conventional If-else operator in c#

I ran 100 million Ternary Operators and 100 million If-Else statements and recorded the performance of each. Here is the code:

Stopwatch s = new Stopwatch();
// System.Diagnostics Stopwatch
int test = 0;
s.Start();
for(int a = 0; a < 100000000; a++)
test = a % 50 == 0 ? 1 : 2;
s.Stop();

s.Restart();
for(int b = 0; b < 100000000; b++)
{
if(b % 50 == 0)
test = 1;
else
test = 2;
}
s.Stop();

Here is the results (ran on an Intel Atom 1.66ghz with 1gb ram and I know, it sucks):

  • Ternary Operator: 5986 milliseconds or 0.00000005986 seconds per each operator.

  • If-Else: 5667 milliseconds or 0.00000005667 seconds per each statement.

Don't forget that I ran 100 million of them, and I don't think 0.00000000319 seconds difference between the two matters that much.

Benefits of ternary operator vs. if statement

Performance

The ternary operator shouldn't differ in performance from a well-written equivalent if/else statement... they may well resolve to the same representation in the Abstract Syntax Tree, undergo the same optimisations etc..

Things you can only do with ? :

If you're initialising a constant or reference, or working out which value to use inside a member initialisation list, then if/else statements can't be used but ? : can be:

const int x = f() ? 10 : 2;

X::X() : n_(n > 0 ? 2 * n : 0) { }

Factoring for concise code

Keys reasons to use ? : include localisation, and avoiding redundantly repeating other parts of the same statements/function-calls, for example:

if (condition)
return x;
else
return y;

...is only preferable to...

return condition ? x : y;

...on readability grounds if dealing with very inexperienced programmers, or some of the terms are complicated enough that the ? : structure gets lost in the noise. In more complex cases like:

fn(condition1 ? t1 : f1, condition2 ? t2 : f2, condition3 ? t3 : f3);

An equivalent if/else:

if (condition1)
if (condition2)
if (condition3)
fn(t1, t2, t3);
else
fn(t1, t2, f3);
else if (condition3)
fn(t1, f2, t3);
else
fn(t1, f2, f3);
else
if (condition2)
...etc...

That's a lot of extra function calls that the compiler may or may not optimise away.

Further, ? allows you to select an object, then use a member thereof:

(f() ? a : b).fn(g() ? c : d).field_name);

The equivalent if/else would be:

if (f())
if (g())
x.fn(c.field_name);
else
x.fn(d.field_name);
else
if (g())
y.fn(c.field_name);
else
y.fn(d.field_name);

Can't named temporaries improve the if/else monstrosity above?

If the expressions t1, f1, t2 etc. are too verbose to type repeatedly, creating named temporaries may help, but then:

  • To get performance matching ? : you may need to use std::move, except when the same temporary is passed to two && parameters in the function called: then you must avoid it. That's more complex and error-prone.

  • c ? x : y evaluates c then either but not both of x and y, which makes it safe to say test a pointer isn't nullptr before using it, while providing some fallback value/behaviour. The code only gets the side effects of whichever of x and y is actually selected. With named temporaries, you may need if / else around or ? : inside their initialisation to prevent unwanted code executing, or code executing more often than desired.

Functional difference: unifying result type

Consider:

void is(int) { std::cout << "int\n"; }
void is(double) { std::cout << "double\n"; }

void f(bool expr)
{
is(expr ? 1 : 2.0);

if (expr)
is(1);
else
is(2.0);
}

In the conditional operator version above, 1 undergoes a Standard Conversion to double so that the type matched 2.0, meaning the is(double) overload is called even for the true/1 situation. The if/else statement doesn't trigger this conversion: the true/1 branch calls is(int).

You can't use expressions with an overall type of void in a conditional operator either, whereas they're valid in statements under an if/else.

Emphasis: value-selection before/after action needing values

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.

In different situations, either may better reflect the programmer's "natural" perspective on the code and make it easier to understand, verify and maintain. You may find yourself selecting one over the other based on the order in which you consider these factors when writing the code - if you've launched into "doing something" then find you might use one of a couple (or few) values to do it with, ? : is the least disruptive way to express that and continue your coding "flow".

Ternary operator is twice as slow as an if-else block?

To answer this question, we'll examine the assembly code produced by the X86 and X64 JITs for each of these cases.

X86, if/then

    32:                 foreach (int i in array)
0000007c 33 D2 xor edx,edx
0000007e 83 7E 04 00 cmp dword ptr [esi+4],0
00000082 7E 1C jle 000000A0
00000084 8B 44 96 08 mov eax,dword ptr [esi+edx*4+8]
33: {
34: if (i > 0)
00000088 85 C0 test eax,eax
0000008a 7E 08 jle 00000094
35: {
36: value += 2;
0000008c 83 C3 02 add ebx,2
0000008f 83 D7 00 adc edi,0
00000092 EB 06 jmp 0000009A
37: }
38: else
39: {
40: value += 3;
00000094 83 C3 03 add ebx,3
00000097 83 D7 00 adc edi,0
0000009a 42 inc edx
32: foreach (int i in array)
0000009b 39 56 04 cmp dword ptr [esi+4],edx
0000009e 7F E4 jg 00000084
30: for (int x = 0; x < iterations; x++)
000000a0 41 inc ecx
000000a1 3B 4D F0 cmp ecx,dword ptr [ebp-10h]
000000a4 7C D6 jl 0000007C

X86, ternary

    59:                 foreach (int i in array)
00000075 33 F6 xor esi,esi
00000077 83 7F 04 00 cmp dword ptr [edi+4],0
0000007b 7E 2D jle 000000AA
0000007d 8B 44 B7 08 mov eax,dword ptr [edi+esi*4+8]
60: {
61: value += i > 0 ? 2 : 3;
00000081 85 C0 test eax,eax
00000083 7F 07 jg 0000008C
00000085 BA 03 00 00 00 mov edx,3
0000008a EB 05 jmp 00000091
0000008c BA 02 00 00 00 mov edx,2
00000091 8B C3 mov eax,ebx
00000093 8B 4D EC mov ecx,dword ptr [ebp-14h]
00000096 8B DA mov ebx,edx
00000098 C1 FB 1F sar ebx,1Fh
0000009b 03 C2 add eax,edx
0000009d 13 CB adc ecx,ebx
0000009f 89 4D EC mov dword ptr [ebp-14h],ecx
000000a2 8B D8 mov ebx,eax
000000a4 46 inc esi
59: foreach (int i in array)
000000a5 39 77 04 cmp dword ptr [edi+4],esi
000000a8 7F D3 jg 0000007D
57: for (int x = 0; x < iterations; x++)
000000aa FF 45 E4 inc dword ptr [ebp-1Ch]
000000ad 8B 45 E4 mov eax,dword ptr [ebp-1Ch]
000000b0 3B 45 F0 cmp eax,dword ptr [ebp-10h]
000000b3 7C C0 jl 00000075

X64, if/then

    32:                 foreach (int i in array)
00000059 4C 8B 4F 08 mov r9,qword ptr [rdi+8]
0000005d 0F 1F 00 nop dword ptr [rax]
00000060 45 85 C9 test r9d,r9d
00000063 7E 2B jle 0000000000000090
00000065 33 D2 xor edx,edx
00000067 45 33 C0 xor r8d,r8d
0000006a 4C 8B 57 08 mov r10,qword ptr [rdi+8]
0000006e 66 90 xchg ax,ax
00000070 42 8B 44 07 10 mov eax,dword ptr [rdi+r8+10h]
33: {
34: if (i > 0)
00000075 85 C0 test eax,eax
00000077 7E 07 jle 0000000000000080
35: {
36: value += 2;
00000079 48 83 C5 02 add rbp,2
0000007d EB 05 jmp 0000000000000084
0000007f 90 nop
37: }
38: else
39: {
40: value += 3;
00000080 48 83 C5 03 add rbp,3
00000084 FF C2 inc edx
00000086 49 83 C0 04 add r8,4
32: foreach (int i in array)
0000008a 41 3B D2 cmp edx,r10d
0000008d 7C E1 jl 0000000000000070
0000008f 90 nop
30: for (int x = 0; x < iterations; x++)
00000090 FF C1 inc ecx
00000092 41 3B CC cmp ecx,r12d
00000095 7C C9 jl 0000000000000060

X64, ternary

    59:                 foreach (int i in array)
00000044 4C 8B 4F 08 mov r9,qword ptr [rdi+8]
00000048 45 85 C9 test r9d,r9d
0000004b 7E 2F jle 000000000000007C
0000004d 45 33 C0 xor r8d,r8d
00000050 33 D2 xor edx,edx
00000052 4C 8B 57 08 mov r10,qword ptr [rdi+8]
00000056 8B 44 17 10 mov eax,dword ptr [rdi+rdx+10h]
60: {
61: value += i > 0 ? 2 : 3;
0000005a 85 C0 test eax,eax
0000005c 7F 07 jg 0000000000000065
0000005e B8 03 00 00 00 mov eax,3
00000063 EB 05 jmp 000000000000006A
00000065 B8 02 00 00 00 mov eax,2
0000006a 48 63 C0 movsxd rax,eax
0000006d 4C 03 E0 add r12,rax
00000070 41 FF C0 inc r8d
00000073 48 83 C2 04 add rdx,4
59: foreach (int i in array)
00000077 45 3B C2 cmp r8d,r10d
0000007a 7C DA jl 0000000000000056
57: for (int x = 0; x < iterations; x++)
0000007c FF C1 inc ecx
0000007e 3B CD cmp ecx,ebp
00000080 7C C6 jl 0000000000000048

First: why is the X86 code so much slower than X64?

This is due to the following characteristics of the code:

  1. X64 has several additional registers available, and each register is 64-bits. This allows the X64 JIT to perform the inner loop entirely using registers aside from loading i from the array, while the X86 JIT places several stack operations (memory access) in the loop.
  2. value is a 64-bit integer, which requires 2 machine instructions on X86 (add followed by adc) but only 1 on X64 (add).

Second: why is the ternary operator slower on both X86 and X64?

This is due to a subtle difference in the order of operations impacting the JIT's optimizer. To JIT the ternary operator, rather than directly coding 2 and 3 in the add machine instructions themselves, the JIT creating an intermediate variable (in a register) to hold the result. This register is then sign-extended from 32-bits to 64-bits before adding it to value. Since all of this is performed in registers for X64, despite the significant increase in complexity for the ternary operator the net impact is somewhat minimized.

The X86 JIT on the other hand is impacted to a greater extent because the addition of a new intermediate value in the inner loop causes it to "spill" another value, resulting in at least 2 additional memory accesses in the inner loop (see the accesses to [ebp-14h] in the X86 ternary code).

Is ternary operator, if-else or logical OR faster in javascript?

The speed difference will be negligible - use whichever you find to be more readable. In other words I highly doubt that a bottleneck in your code will be due to using the wrong conditional construct.

Ternary operator vs if statement: compiler optimization

Mats Petersson suggestion is generally the best "Write the most readable variant".
However, if you are trying to write optimal speed performance code, you need to know more info about your computer and processor. With some machines, the first will run faster (highly pipelined processors: no branching, optimized ternary operator). Other machines will run quicker with the second form (simpler).

Ternary Operator vs. If/Else in Objective C

Although there may be minor differences in compiled code, there is no reason other than readability to prefer one way to the other.

One advantage of the if/else approach is that you can set up multiple variables with a single condition:

Object *myObj1;
Object *myObj2;
if (boolean) {
myObj1 = trueValue1;
myObj2 = trueValue1;
else {
myobj1 = falseValue2;
myobj2 = falseValue2;
}

Since boolean in Objective-C is a numeric type with values 0 and 1, there is an approach that does not rely on conditionals at all:

Object *valArray[] = { falseValue, trueValue };
Object *myObj = valArray[boolean];


Related Topics



Leave a reply



Submit