0

I was wondering if the two different codes below performed better than the other. They do the same function. I would think that when implementing this code you would want the if statement to contain the argument that would occur more frequently.

Option #1

if(hoursWorked <= 40){
    workedOvertime = 0;
}
else{
    workedOvertime = 1;
}

Option #2

if(hoursWorked > 40){
    workedOvertime = 1;
}
else{
    workedOvertime = 0;
}

2 Answers2

5

On the Godbolt Compiler Explorer you can see what various compilers do with both versions, as well as KamilCuk's "option 3".

Guess what: with, for instance, the gcc compiler on x86-64 and optimization on, they all produce precisely the same code. So it makes no difference whatsoever.

(In particular, they all use a conditional set instruction that doesn't require a jump whether the condition is true or false.)

This is typical: modern compilers are smart enough to see that these are all equivalent, and to generate the best code no matter which way you wrote it. So it is a waste of time to think about such things. If one way seems to be clearer for someone reading the program, do it that way. If you don't think it makes any difference to a human, then just pick a way and move on.

If you have reason to believe that one case is more common than the other, you can use something like gcc's __builtin_expect to hint this to the compiler. That way, if it is possible to optimize one branch to be faster than the other, the compiler will do so for the more common one. There can also be ways to use profiling to measure which branch is taken more often, and automatically inform the compiler of this. But the compiler normally won't try to draw such inferences just from your choice of which branch is the "then" and which is the "else".

However, in this case, using __builtin_expect doesn't change anything, so both branches are already optimized as best the compiler knows how, and it doesn't know any way to make either one faster, even at the expense of making the other one slower.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • You should not conclude from one experiement with `__builtin_expect` that the compiler cannot improve this code. There are architectures in which the branch instructions can contain hints, which the process may use to decide which instructions to fetch next, and GCC could use those hints in such an architecture even though you see no difference in the architecture you tested, which does not have those hints. Additionally, GCC might make alternate decisions in various circumstances you did not test, such as decide which case to make the branch target versus which is the fall-through. – Eric Postpischil Mar 06 '20 at 00:42
  • 1
    @EricPostpischil: Sure, my claim is certainly limited to "this case": this exact code on this target with this compiler, version and options. The situation could certainly be different with real-life code, even if it looks similar to this example. – Nate Eldredge Mar 06 '20 at 00:46
  • How doe the compiler handle `workedOvertime = (hoursWorked > 40);`? Might that encourage a branchless implementation? – Lee Daniel Crocker Mar 06 '20 at 00:47
  • @LeeDanielCrocker: I included that in my test, if you'll follow the link. They all three get branchless code in this example. In a more complicated example, that version *might* be more likely to get a branchless implementation, but it's hard to say anything general. – Nate Eldredge Mar 06 '20 at 00:49
2

It is almost impossible to give an answer to this question as it will depend on the machine architecture, compiler, compiler options (e.g. optimisations) and a bunch of other things. For example one architecture I worked on we were told that conditionals using "not" were faster. I don't know if it's true, but if (hoursWorked <= 40) would have been written if (!(hoursWorked > 40)). I know: harder to read but that is what we were told to do!

It's likely that this is a case of Premature Optimization. i.e. Trying to fix this one tiny part of your code with the expectation of huge performance gains. That isn't going to happen.

For example, in 68K assembler, a "BEQ" (Branch on equal) takes 10 cycles if the condition is met and 8 or 12 if it isn't (depending on whether you are using bytes or words) - so that is 3 possible answers just for an "if" depending on types and which way the code goes. By comparison a single multiplication takes 70 cycles. If you get to IO then we're probable into thousands (if not more).

So unless you have a really good reason to care about stuff at this level then don't ! It's a waste of time. Write good readable code and then come back to this if and when you have a performance issue.

In reality all possible options are going to be very very close to each other and pale into insignificance compared to other things in your program. Unless you have a very specific case (e.g. a huge number of calls in a real time environment on an under powered embedded system) then it will make no difference.

John3136
  • 28,809
  • 4
  • 51
  • 69
  • How would you know whether this is premature optimization or not? OP does not indicate what stage of development they are in or at what stage they will use this information. There are people who do make huge numbers of calls in a real-time environment or write code for under-powered embedded systems or who will do so in the future, and those situations can be quite lucrative for people who understand them well. So this information should not be undervalued. – Eric Postpischil Mar 06 '20 at 00:45
  • 2
    @EricPostpischil Because the kind of person in a situation where it did matter would likely ask a more targeted question. Are you seriously suggesting that it isn't premature optimization in 99.999% of cases and that I've addressed an example of the .001 in my answer? – John3136 Mar 06 '20 at 02:39
  • @EricPostpischil We'll have to agree to disagree. I think my answer is right in over 99% of cases. I've acknowledged and given an example of cases where it may not be right, so I think it is useful to over 99% of readers. I don't see how I'm telling people not to ask. I never said it was an invalid question. I didn't downvote the question. I answered it in a way that is "right" for most people. I think your use of "shameful" is inflammatory, so I'll leave the discussion here. Others can up vote or downvote as they see fit. – John3136 Mar 06 '20 at 03:35
  • No, we do not have to agree to disagree. Telling people not to care is promoting ignorance. Writing high-performance code does in fact require consideration about optimization at all stages of design, and people cannot know what matters when unless they have information. – Eric Postpischil Mar 06 '20 at 03:41