0

Do compilers compile a simple ternary statement to the same thing that they would compile a simple if else statement? Also, why would a compiler be designed to compile them differently?

For example, would this:

int a = 169; int b = 420; int c; c = a > b ? 42:69; 

compile to the same thing as this:

int a = 169; int b = 420; int c; if(a>b) c = 42; else c = 69; 

This question is not about which is better or when to use each one, so please don't include that in your answer.

16
  • 4
    Do you know how to look at generated bytecode? stackoverflow.com/questions/3315938/… Commented Feb 3, 2016 at 23:32
  • 1
    Are you asking about some particular compiler? The two snippets are semantically equivalent and it would be perfectly legal for a compiler to produce the same bytecode for the two snippets. Commented Feb 3, 2016 at 23:36
  • 1
    There are two levels of compilation in a typical Java implementation: from Java source code to Java bytecode, and from Java bytecode to machine code. Which are you asking about? If you care about speed, the second is more important than the first. Commented Feb 3, 2016 at 23:54
  • 2
    @Andreas Regardless of wether it impacts my, it is still an interesting question. One typically asks questions when they do not know the answer to something. Even if the answer is useless, it may help you understand something relevant in the future. Commented Feb 4, 2016 at 0:02
  • 1
    @Andreas different statements, yes. But, they do exactly the same thing. Commented Feb 4, 2016 at 0:08

4 Answers 4

6

First of all, this is implementation-dependent. The JLS does not specify exactly how a specific snippet or operation must be compiled, as long as the bytecode satistifes the Java Language Specification when run on a VM supporting the Java Virtual Machine specification. A different compiler can generate bytecode that is different from the examples given, as long as it gives the same result when run on a compliant JVM.

On Java 8's javac (1.8.0_65), the code is not the same for the conditional operator, and the if-else.

The ternary operator controls which value is pushed to the stack, and then the value on the top of the stack is stored unconditionally. In this case, if a>b, 42 is pushed and code jumps to the istore, else 59 is pushed. Then whatever value is on top is istored to c.

In the if-else, the conditional controls which istore instruction is actually called.

Notice however that in both cases the instruction is "compare less than or equal" which jumps to the else branch (continuing the if branch otherwise).

Below can be seen the bytecode generated by various compilers. You can get it yourself using the javap tool available in an OpenJDK JDK (example command-line javap -c ClassName)

javac with ternary:

 public static void main(java.lang.String...); Code: 0: sipush 169 3: istore_1 4: sipush 420 7: istore_2 8: iload_1 9: iload_2 10: if_icmple 18 13: bipush 42 15: goto 20 18: bipush 69 20: istore_3 21: return 

javac with if-else:

 public static void main(java.lang.String...); Code: 0: sipush 169 3: istore_1 4: sipush 420 7: istore_2 8: iload_1 9: iload_2 10: if_icmple 19 13: bipush 42 15: istore_3 16: goto 22 19: bipush 69 21: istore_3 22: return } 

However, with ecj, the code is even more odd. Ternary operator conditionally pushes one or the other value, then pops it to discard it (without storing):

Code: 0: sipush 169 3: istore_1 4: sipush 420 7: istore_2 8: iload_1 9: iload_2 10: if_icmple 18 13: bipush 42 15: goto 20 18: bipush 69 20: pop 21: return 

ecj with if-else somehow optimizes out the pushes/stores but still includes an oddball comparison (mind you, there are no side effects to the comparison that need to be retained):

Code: 0: sipush 169 3: istore_1 4: sipush 420 7: istore_2 8: iload_1 9: iload_2 10: if_icmple 13 13: return 

When I add a System.out.println(c) to foil this unused-value discard, I find that the structure of both statements is similar to that of javac (ternary does conditional push and fixed store, while if-else does conditional store).

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

7 Comments

This is implementation dependent. It would be perfectly fine to compile both snippets to the same bytecode. You might want to mention which compiler you're talking about here.
Why would your compiler be designed to compile them differently? Is there an advantage?
@10Replies Not necessarily an advantage, but a compiler might be designed based on a given structure or pattern, and certain outputs will fit its parsing and conversion steps better.
Those bytecode samples don't prove anything, because all modern jvms use jit compilers that will optimize on the fly. Additionally, even a minor version update of your jvm may change the compiler's behaviour.
Your bytecode samples feed the false hope that there is a definitive answer to the question. Why provide these samples if the answer is "it depends on god-knows-what"?
|
2

To a compiler, the following is one statement with a ternary expression:

c = a > b ? 42 : 69; 

To a compiler, the following is three different statements:

if (a > b) { // statement 1 c = 42; // statement 2 } else { c = 69; // statement 3 } 

Each statement is compiled to byte code independently of other statements.

Analyzing separate statements to detect commonality, and rearranging the code to generate "better" byte code is called optimization, and is entirely optional.

Most people compile without optimization, because compile-time optimization is fairly ineffective vs. run-time optimization, and compile-time optimization prevents (complicates) debugging code, since the generated code would no longer be directly related to the source code line numbers.

Example: If left-hand side was instead myObj.myField, then it could generate NullPointerException if myObj is null. If compiler rearranged code, any stack trace would not be able to tell which line caused the exception.

Comments

0

There is no way to answer this question at a broad scale. VM languages such as java will optimize bytcode at runtime using very complex algorithms. Please see What does a just-in-time (JIT) compiler do?.

Purely compiler-based languages are somewhat more predictable, but then we would need to look at individual combinations of language, os, compiler version etc.

Trust your compiler and/or virtual machine to optimize easy stuff like that for you. It can do much more sophisticated optimizations.

5 Comments

So, your answer is that there is no answer? Does that count as an answer? Can you at least answer the second part of my question?
I cannot make any sense of your second question: "Why would a compiler be designed to compile them differently". Yes, why indeed? Nobody will design a compiler to solve the same problem in different ways. The ternary operator is all about coding convenience, nothing more.
Why would you want to program a compiler to compile the ternary and the if/else differently?
@10Replies You wouldn't, unless it fell out of how the compiler is organized internally.
I can't think of any reason.
0

Nobody has mentioned OpenJDK... You can look at the code that compiles Java (OpenJDK is merging with OracleJDK as of JDK11, which is why you can only view up to JDK9's source). Here's my abbreviated answer from this post: How exactly does JVM compile ternary operators? Should I be concerned about different api versions? I looked at the source for JDK9, which as far as I know is the latest source for the OpenJDK compiler you can look at without paying for a license.

What Javac Source Code Actually Does

For the bold the brave and the few

I decided to look at the source for javac... It took awhile, but with a little help from their hitchhiker's guide to javac, I was able to find the one line that definitively determines what happens. Check it out (Line 914): https://hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Do you see that? Let me clarify this a bit, lines 905-918 say this:

 /** Expression1Rest = ["?" Expression ":" Expression1] */ JCExpression term1Rest(JCExpression t) { if (token.kind == QUES) { int pos = token.pos; nextToken(); JCExpression t1 = term(); accept(COLON); JCExpression t2 = term1(); return F.at(pos).Conditional(t, t1, t2); } else { return t; } } 

The comment tells us this is what they use for parsing ternary expressions, and if we look at what it returns, it returns a conditional where t is the expression being evaluated, t1 is the first branch, and t2 is the second branch. Let's take a look at Conditional just to be sure. It looks like Conditional is being called from F, which if we dig a little deeper we can find out is the TreeMaker, what is a tree maker you may ask? Well, it's specifically an Abstract Syntax Tree which is often used as an intermediate representation of the code being parsed (check it out here https://en.wikipedia.org/wiki/Abstract_syntax_tree). Anyways, if we look inside that file (https://hg.openjdk.java.net/jdk9/jdk9/langtools/file/65bfdabaab9c/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java) we can see at lines 306-313 this:

 public JCConditional Conditional(JCExpression cond, JCExpression thenpart, JCExpression elsepart) { JCConditional tree = new JCConditional(cond, thenpart, elsepart); tree.pos = pos; return tree; } 

Which further confirms exactly what we thought, that a ternary expression is compiled exactly the same as an if-else statement (otherwise known as a conditional statement) :) I encourage anyone interested to take a look at the hitchhiker's guide (https://openjdk.java.net/groups/compiler/doc/hhgtjavac/index.html) and the code, it's actually really interesting to see how even a commercial grade compiler follows a lot of the principle things that you learn about in your standard compiler course at college.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.