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.

10 Replies
  • 426
  • 9
  • 25
  • 4
    Do you know how to look at generated bytecode? http://stackoverflow.com/questions/3315938/is-it-possible-to-view-bytecode-of-class-file – Jeroen Vannevel Feb 03 '16 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. – aioobe Feb 03 '16 at 23:36
  • @aioobe Not asking about a specific compiler, I was just wondering about commonly used compilers. – 10 Replies Feb 03 '16 at 23:40
  • 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. – Jeffrey Bosboom Feb 03 '16 at 23:54
  • @JeffreyBosboom I assume that if the compiler compiles the ternary and the if/else the same in the first step, the second step would also be the same. The first level of compilation is what I am asking about. – 10 Replies Feb 03 '16 at 23:56
  • Because you should care about functionality and performance, and if those are your concerns, your question should be more specific about the actual concern. Whether they generate same or different byte code has no impact on you. Even if byte code is different, the second-step compilation to machine code may still produce the same result, so actual byte code is mostly meaningless. – Andreas Feb 03 '16 at 23:59
  • Please give reasoning for your down votes by commenting, so that I know what to do better next time. – 10 Replies Feb 04 '16 at 00:00
  • 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. – 10 Replies Feb 04 '16 at 00:02
  • @Andreas Whether or not it has an impact on the performance of his application or the code he writes, this is an acceptable academic question about what compilers output in practice. Although it doesn't show research, it does specify an exact inquiry that is in this site's scope, gives compilable code, and is answerable. – nanofarad Feb 04 '16 at 00:02
  • My *guess* for downvotes are answered by first comment. Minimal effort on your part to the a simple `javap -c` command could have answered your own question. – Andreas Feb 04 '16 at 00:02
  • @Andreas, the javap -c command will tell me **why** the compiler was designed to compile each statement differently? – 10 Replies Feb 04 '16 at 00:04
  • @10Replies No, but reading the source code of a given compiler will likely do so. – nanofarad Feb 04 '16 at 00:06
  • Actually. I would have been more surprised if they did compile the same, and would want to know why that would be. They compile differently because they are different statements, and no optimization was applied to make it otherwise. Even with compiler optimization turned on, there is no *generic* documentation of what such optimizations would be. – Andreas Feb 04 '16 at 00:07
  • 1
    @Andreas different statements, yes. But, they do exactly the same thing. – 10 Replies Feb 04 '16 at 00:08
  • 1
    The result *in this case* is the same. But `if (...) { a=1; } else { b=1; }` would not be. Only the fact the your blocks are very simple and use the same variable on left-hand side of assignment makes them so, and it would require compiler optimization logic to detect that. Without optimization turned on (which is rarely done), and/or the compiler developer explicit coding for that particular scenario, it won't happen. If I was the compiler developer and you asked me why I didn't, I would ask you in return why I should. – Andreas Feb 04 '16 at 00:14
  • agree with @Andreas -- it's highly unlikely that the compiler can see the equivalence. – ZhongYu Feb 04 '16 at 00:42

4 Answers4

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

nanofarad
  • 40,330
  • 4
  • 86
  • 117
  • 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. – aioobe Feb 03 '16 at 23:34
  • Why would your compiler be designed to compile them differently? Is there an advantage? – 10 Replies Feb 03 '16 at 23:44
  • @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. – nanofarad Feb 03 '16 at 23:45
  • 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. – Andreas Vogl Feb 04 '16 at 00:01
  • @AndreasVogl Regardless of any JIT involvement, the requirement for a Java bytecode compiler is to generate valid bytecode, and my examples clearly back my claim that it's implementation-specific any may but need not vary. Notice that I make no claim as to *any* machine code that is run on *any* JIT, hypothetical or otherwise. – nanofarad Feb 04 '16 at 00:05
  • 1
    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"? – Andreas Vogl Feb 04 '16 at 00:09
  • @AndreasVogl What *specific* false hope do I provide? My claim is that it's implementation defined and various implementations exist with their own distinct results; I make no claim about a specific difference being present in all compilers. Don't read too far into meaning that isn't there. (Anyway, if you have a sample countering my point from a compiler I did not use, I would love to see it) – nanofarad Feb 04 '16 at 00:13
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.

Andreas
  • 154,647
  • 11
  • 152
  • 247
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.

Community
  • 1
  • 1
Andreas Vogl
  • 1,776
  • 1
  • 14
  • 18
  • 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? – 10 Replies Feb 03 '16 at 23:46
  • 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. – Andreas Vogl Feb 03 '16 at 23:53
  • Why would you want to program a compiler to compile the ternary and the if/else differently? – 10 Replies Feb 03 '16 at 23:53
  • @10Replies You wouldn't, unless it fell out of how the compiler is organized internally. – user207421 Feb 03 '16 at 23:56
  • I can't think of any reason. – Andreas Vogl Feb 03 '16 at 23:56
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.