12

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.

8
  • 3
    Duplicit question: stackoverflow.com/questions/3565368/ternary-operator-vs-if-else Commented May 22, 2013 at 16:08
  • 1
    I'm not a compiler programmer, so I don't really know, but it could be possible that it evaluates both of the sides of the ternary operator and so skips branching (which would make it fast). Commented May 22, 2013 at 16:10
  • 1
    If anything, val is set to 0 when it already is zero, which is unnecessary in the first case. It's very unlikely that the compiler will do anything different tho'. Write the most readable variant. Commented May 22, 2013 at 16:13
  • @Hassedev No, the ?: conditional operator is not allowed to evaluate both of its arguments. Commented May 22, 2013 at 16:24
  • @unwind Ok, thanks for the clarification. I remember that it did evaluate both sides in GLSL but had no idea of what it does on the CPU and C++. Commented May 22, 2013 at 16:27

6 Answers 6

6

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

Sign up to request clarification or add additional context in comments.

7 Comments

The if statement is guaranteed to be at least as fast with even a mediocre optimizing compiler, because it doesn't zero initialize. If it did the same thing, then there would be no performance difference.
@strcat The "guaranteed to be at least as fast" is certainly not in the C spec. The hardware architecture and software techniques of today may easily advance tomorrow and provide a different paradigm. Scalar processors and simply embedded processors offer a wide range of possibilities.
A modern compiler sees both primitives as the same thing, and there is no use case for distinguishing between them. Your answer presents it as a hardware issue but the generated code will always be the same in practice. This isn't going to change as architectures evolve. The compiler will do the same reasoning to decide how to perform the branch regardless of the code style - which is all this is. Sure, a compiler could decide to do optimizations based on whether you use spaces or tabs to indent, but that's not useful as a practical answer.
I'm not making any claims about the C specification. I simply pointed out that an optimizing compiler of even mediocre quality guarantees that these will be treated the same way. If it doesn't treat them the same way, it has a performance bug.
@strcat Interesting assertion: code compiles the same way, if not, the compiler is wrong, not the assertion. Consider an ancient (or novel new platform) using sign-magnitude integers: OP's 2 codes could have different functionality given there are + and - zeros. The first may set val to one of the zeros, the 2nd allows both zeros to persist. The point is not to champion a return to non-2's complement, but to point out C compliant architecture differences can impact supposed equal compilation guarantees.
|
6

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);
}

4 Comments

Where did you learn that the ternary operator is branchless?
Ternary operators are not branchless. There are implementations like bitselect that implement ternary operations for integer in a branchless way (like i've shown here). As you can see, there's no actual ternary operator in the code.
Ah, now I see that you implemented it yourself! Nice.
Modern optimizing compilers will generate branchless code for simple ternarys. Often times more efficient than any bit twiddling non-branching implementation since the semantics aren't hidden from the compiler. You have to check your compiler to see what it does.
4

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.

3 Comments

In C++ you could imagine a copy assignment taking place. For example: "Object a = Object(default); a = (condition) ? Object(var) : Object(default)". Will the compiler be smart enough and aggressive enough to remove the assignment? Sounds dangerous since stuff can happen in constructors. Would it be different for plain old data?
"don't nest ternary statements"... unless you have no choice (constexpr comes to mind).
there is an optimisation called "copy elision", which allows the compiler prevent some unnecessary copy along the way. I think Object(...) will neve be built since it's a nameless temp and assigned to a variable. en.cppreference.com/w/cpp/language/copy_elision cpp-next.com/archive/2009/08/want-speed-pass-by-value
2

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.

4 Comments

Just trying to understand where readability affects performance. Using a ternary might improve readability in a few occasions but can also induce a performance cost. Meaning that the if-assign would be logically faster but if I wanted to use a ternary to improve readability I would go against that simple logic. If I do that, can the compiler optimize it? It's pretty obvious the ternary-else branch is useless for PoD. But again, when files are mapped to memory or when programming drivers, each write in memory can have an impact... I need to understand how most compiler work in this respect.
Most Compilers will optimize ternarys and if/else blocks to exactly the same machine code. Also a ternary almost never makes something more readable, in my humble opinion.
I think you are right for the readability, however it's subjective and I've seen it used for such justification. Understand that I don't wanna compare ternary to if-else, just to a single if. Meaning the else condition is never taken with the if, whereas it is taken with the ternary... or is it ? (it's my question). It's hard to figure out if the compiler wants to be logical and pedantic or aggressive and optimized. I'm just curious.
Before if constexpr came along, the ternary operator was often the only way to declare some compile-time constants. One of my first exercises in recursive templates was a way to calculate the gcd at compile time, and it required lots of ternary operators.
2

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

Comments

0

One big upside of ternary is that you can declare the resuling variable to be const, helping with const correctness at function scope:

const auto value = (condition) ? "true" : "false";`

as opposed to:

auto value = "";

if (condition) {
  value = "true";
} else {
  value = "false"
}

As many others have stated, this isnt likely to cause the compiler to generate different code but it helps to avoid bug and unwanted behaviour caused by reassigning to value.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.