if else Vs ternary

Hi Guys,

The other day I was looking at my code to save few CPU clocks. It was very interesting for me that ternary operator takes extra 'mov' as compare to ordinary 'if else'. To my understanding it always takes extra mov for base pointer in case of ternary operator and I have no idea why it is so. Would love to know if someone can share why there is an extra 'mov'.

Below is food for thought:

Consider 'r' and 'i' are 'int' type variables. r = i==0 ? 2 : 3; cmpl $0x0,-0x4(%rbp) jne 0x100000f2c movl $0x2,-0x14(%rbp) jmp 0x100000f33 movl $0x3,-0x14(%rbp) mov -0x14(%rbp),%eax mov %eax,-0x8(%rbp)

And here we do again with classic 'if else' structure.

if(i==0) cmpl $0x0,-0x4(%rbp) jne 0x100000f30 r = 2; movl $0x2,-0x8(%rbp) jmp 0x100000f37 r =3; movl $0x3,-0x8(%rbp)

I have complied this code via latest gcc.

Reply to
Ali
Loading thread data ...

Did you use the -O flag to run the code optimizer ? With optimization I get equivalent code for both cases.

Reply to
Arlet Ottens

I had following in command line:

"-O0 -g3 -Wall -c -fmessage-length=3D0" but its same for me. Can you share your compile options?

cheers, ali

Reply to
Ali

When using -O0 GCC (intentionally) produces horrible code. You'd typically want to use at least -O1 for real work.

-a

Reply to
Anders.Montonen

-O0 is without optimization (same as default without -O), and then you can expect fairly naive code translations such as the one above.

In most cases, either -O2 or -Os are good standard flags to use, and both will work on the code above to produce identical results for if/else vs ternary.

Reply to
Arlet Ottens

I

Thank you folks.

formatting link

So, I can conclude that the extra mov is just for debugging purpose.

regards, ali

Reply to
Ali

The best choice of code sequences is highly dependent on the target. I am guessing that you are using some sort of x86 processor, in which case neither of these sequences are any good - you want to avoid branches.

As others have pointed out, using "-O0" (or no -O flag) is a sure way to produce horrible code. You should always use -O (or -O1) as an absolute minimum - contrary to some old myths, debugging is usually easier with

-O1 than -O0. For real working code, -Os or -O2 are more common (why would one get a top-rate optimising compiler, and then choose not to let it optimise the code?). You also want to use an appropriate -march flag to make use of your processor (again, why buy a fast modern processor, then treat it as a 15 year old 386?).

Secondly, code like this taken out of context is not a realistic test. Other factors such as how the compiler can work with surrounding code, and how the code and data fits with the processor cache, will have a much bigger influence than a few instructions. If you want to get faster code (other than by picking sensible compiler flags), you have to do real profiling and see where your bottlenecks are.

Finally, a statement like "r = i==0 ? 2 : 3;" should be taken out back and shot. At the very least, put some parenthesis in. Making your code clear trumps any speed issues in almost all cases.

Reply to
David Brown

e
g

ack

ode

David:

Good take. The second thought you mentioned with respect to context is very true.

Ok, I have no word to say what complier developers face challenges to write one. The only thing I can image is that the -Optimization thing is just for debugging purpose. Or is there any other good reason to waste cpu clock?

regards, ali

Reply to
Ali

I don't know if it's for debugging. I think it's just a consequence of the simplistic code generation that you get without any optimization.

Supposedly, the -O0 option reduces compile time, but this isn't necessarily so either. Because of the larger intermediate code size, the

-O0 can cost more time than the -O2 option.

Reply to
Arlet Ottens

In a few cases different optimization levels will light up different compiler bugs, and changing the optimization level will help.

Really highly optimized code _can_ confuse a debugger -- I don't know how much, but after reading Arlet's comments I'll be trying different levels.

Finally, if you weren't careful with your code (specifying 'volatile', 'static', etc., correctly everywhere), lower optimization levels will sometimes bury _your_ bugs under the used kitty litter. This probably happens more often than actual compiler bugs, but that's not what the problem will get blamed on. Certainly I've seen it lead a project team to use -O0 or similar to make things work.

--
Tim Wescott
Wescott Design Services
 Click to see the full signature
Reply to
Tim Wescott

=A0I

se

to

lute

y

et

flag

to

t back

r code

Get rid of such team and get to know new souls.

All I want to know is *why* we have an extra 'mov', from all responses so far; can fairly conclude; give a breath to debugger.

//ali

Reply to
Ali

The facile answer is because the compiler uses different code to generate responses from ternary expressions than from if-then-else strings. Beyond that, I think you'd need to find a gcc developer and pin them to the wall. There's either some odd corner case where the ternary operator needs that move to work right, or the two bits of code were just written by two different people with two different styles.

--
Tim Wescott
Wescott Design Services
 Click to see the full signature
Reply to
Tim Wescott

That's the wrong question, because it presumes that there must be reason for that. There is none. No compiler needs any particular reason to do anything in any particular way, aside from getting the compiled code to do what it's defined to do.

The message you've failed to understand is that gcc -O0 is not intended for _any_ particular usage. You're not supposed to look at its output, much less draw any conclusions from what you see. Just forget it.

No. That's the wrong conclusion, and it wasn't really sugggested by any of the answers you got, either.

Reply to
Hans-Bernhard Bröker

Try optimising for size as well in your experiments; I managed to confuse gdb recently with some C code.

It was a ARM7 board, with a recent gcc build based on the Yagarto build scripts. I was using gdb 7.1 with a ddd frontend.

Several pointer variables were defined within main(). Each pointer was a (different) struct datatype for each peripheral I was working with and the pointer value was the base of the registers for the peripheral.

When examined with ddd, some of the pointers had the expected values, but some appeared to be pointing to various addresses, including within the program flash memory area.

The correct values were actually within the pointers because that part of the code was running correctly.

And yes, I generally debug with optimisation disabled if possible as I am aware of the issues; however I wanted to see if I could reliably debug with optimisation enabled in this version of gcc. I now have my answer. :-)

Simon.

--
Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP
Microsoft: Bringing you 1980s technology to a 21st century world
Reply to
Simon Clubley

Did you verify from the disassembly that the variables had not simply been optimized away?

If you completely disable optimizations the code is usually awful, and a lot larger and slower than expected. On the other hand, if you turn on full optimizations (-O2 and beyond) the resulting object code sometimes bears little resemblance to the source code, and can be difficult to debug. The trick seems to be to use a moderate level optimization (ie.

-O1) which usually produces readable, reasonably performing code.

-a

Reply to
Anders.Montonen

Something that I've noticed in heavily optimized code is that the compiler shoves variables into registers, and the actual variable lifetime is as short as possible. But the debugger doesn't get told that -- it just gets told what register the variable is in.

So your 'pointer' gets read, and presented as such by the debugger, but in reality it's a counter, or a hash, or something. Then when it's actually used (perhaps too briefly to register with the debugger) it's got the right value -- until it's something else again.

--
Tim Wescott
Wescott Design Services
 Click to see the full signature
Reply to
Tim Wescott

To answer your question (and Tim's related observations), IIRC, then yes I did. BTW, I forgot to mention I had also marked the variables as volatile as they were pointers to device registers.

I've also found in the past that gdb reports when you cannot do something with a variable because it has been optimised in some way.

Next time I am debugging embedded code, I will try -O1 and see what happens. (I thought I had already tried it in the case I mentioned, but I cannot be sure now.)

Simon.

--
Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP
Microsoft: Bringing you 1980s technology to a 21st century world
Reply to
Simon Clubley

Please be careful with pointers: It makes a difference whether the volatile entity is the pointer or the target the pointer points to.

--

Tauno Voipio
Reply to
Tauno Voipio

There are several good reasons for not using the same optimisation flags all the time.

Different settings provide a different balance. For example, -Os and

-O2 are mostly the same, except -Os aims towards smaller code, while -O2 will use more code if it runs faster. Do you want smaller code or faster code? That's the user's choice - not the compiler developer's. And depending on the balance between memory speed and processor speed, which the compiler developer cannot know about, -Os /may/ be faster than

-O2.

Then there are all sorts of extra optimisation flags in gcc. Some of these will improve some code but be worse for other code. Some may make assumptions about your code that are not necessarily true (such as different aliasing restrictions).

Smarter optimisations can make debugging more difficult. You can't (easily) put a breakpoint on a line in a function if that function is inlined multiple times in different parts of the target code. You can't view a variable if the optimiser has figured out a way to remove if from the target code. Too /little/ optimisation can make debugging difficult too, especially at the assembler level - it can be hard to follow the action when all your locals are on the stack instead of registers.

Smarter optimisations take time. For small programs or embedded systems, that's seldom a problem - but if you have big programs, a slow compile time quickly gets annoying.

Smarter optimisations are also more likely to have bugs than other parts of the compiler - they are less used, and more complex. However, it is unlikely that you'll hit a bug unless you pick the weirdest optimisation flags, and those that are considered risky are marked as experimental in the manual.

If you have bugs in your code, smarter optimisations can sometimes trigger them. Things like missing volatiles or memory barriers, or alias errors through typecasts, are typical causes of "the program only works with -O0 or -O1".

Reply to
David Brown

Interestingly, gcc (version 4.4.3) using -O2 manages to avoid branches by clever use of the carry flag and subtract instruction:

cmpl $1, 4(%esp) sbbl %eax, %eax addl $3, %eax movl %eax, r

Reply to
Arlet Ottens

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.