简体   繁体   中英

Ternary operator vs if statement: compiler optimization

Is this:

int val;  
// ...
val = (val != 0) ? otherVal : 0;

less efficient than this:

int val;
//...
if (val != 0)
    val = otherVal;

?

Are compiler able to optimize the ternary operator? The intent is clear, is there any way it could be wanted to actually write 0 to memory? Maybe when memory is mapped to a file?

Can we assume it doesn't matter?

EDIT: The point is to set a variable to some value if one condition is met. There is no wanted else branching. which is why I ask if a ternary (with obligatory else branch that is supposed to make a copy) will be less efficient or optimized.

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

Your compiler will optimize it. In the end, there is little to no difference in performance.

There is, however, a big difference in readability. Sometimes the ternary operator can help to remove many lines of code that don't add very much in clarity.

In other cases the if statement is clearer and easier to follow.

Reducing code to a ternary statement but then having to add a ton of comments in order to maintain clarity is counterproductive.

And by all the gods of coding, please don't nest ternary statements.

You could use a branchless ternary operator, sometimes called bitselect ( condition ? true : false).

Don't worry about the extra operations, they are nothing compared to the if statement branching.

bitselect implementation:

inline static int bitselect(int condition, int truereturnvalue, int falsereturnvalue)
{
    return (truereturnvalue & -condition) | (falsereturnvalue & ~(-condition)); //a when TRUE and b when FALSE
}

inline static float bitselect(int condition, float truereturnvalue, float falsereturnvalue)
{
    //Reinterpret floats. Would work because it's just a bit select, no matter the actual value
    int& at = reinterpret_cast<int&>(truereturnvalue);
    int& af = reinterpret_cast<int&>(falsereturnvalue);
    int res = (at & -condition) | (af & ~(-condition)); //a when TRUE and b when FALSE
    return  reinterpret_cast<float&>(res);
}

This is mostly a duplicate of Ternary operator ?: vs if...else

For most compilers the efficiency will be the same and the compiler will optimize the ternary operator just like it optimizes the if/else statement. That said, I prefer if statements as they make the code much easier to read at a quick glance.

To answer your other questions. I'm not sure what you mean, if you are just setting one integer or variable to 0, then there is no faster way other than setting it to zero like you have above.

if you had an array of variables, you could use memset(ptr, 0, size*sizeof(TYPE)) , which would probably be fastest if you had an array of variables you wanted to set to zero. Or perhaps std::fill_n

I'm not sure what you're trying to achieve with the logic above, but it seems a little strange. There are ways to arrange code that would mean you probably wouldn't need a conditional in there at all, but it's hard to say for your situation without seeing a bit more of the code.

In all honesty, unless you're doing this operation billions of times, this is probably very pre-mature optimisation and you should concentrate on readability.

Tools like "compiler explorer" are great at answering questions like this. Fixing the bug in your code and comparing the following two snippets we see they produce identical assembly at -O1 and higher.

void trinary(int& val, int otherVal) {
    val = (val != 0) ? otherVal : 0;
}

void nontrinary(int& val, int otherVal) {
    if(val != 0) {
        val = otherVal;
    }
    else {
        val = 0;
    }
}

trinary(int&, int):
        mov     eax, DWORD PTR [rdi]
        test    eax, eax
        mov     eax, 0
        cmove   esi, eax
        mov     DWORD PTR [rdi], esi
        ret
nontrinary(int&, int):
        mov     eax, DWORD PTR [rdi]
        test    eax, eax
        mov     eax, 0
        cmove   esi, eax
        mov     DWORD PTR [rdi], esi
        ret

What's interesting is that at -O0 they do not produce identical output. At -O0 the compiler uses eax to explicitly store the result of the trinary operator, and then copies eax into the correct register before returning. The non-trinary version does the assignment directly.

trinary(int&, int):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        test    eax, eax
        je      .L2
        mov     eax, DWORD PTR [rbp-12]
        jmp     .L3
.L2:
        mov     eax, 0
.L3:
        mov     rdx, QWORD PTR [rbp-8]
        mov     DWORD PTR [rdx], eax
        nop
        pop     rbp
        ret
nontrinary(int&, int):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        test    eax, eax
        je      .L5
        mov     rax, QWORD PTR [rbp-8]
        mov     edx, DWORD PTR [rbp-12]
        mov     DWORD PTR [rax], edx
        jmp     .L7
.L5:
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], 0
.L7:
        nop
        pop     rbp
        ret

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM