29

I wonder if JVM/javac is smart enough to turn

// This line...
string a = foo();

string foo()
{
  return bar();
}

string bar()
{
  return some-complicated-string computation;
}

into

string a = bar();

Or strip unnecessary call to foo() in release case (because unreachable code):

string a = foo(bar());

// bar is the same
...

string foo(string b)
{
  if (debug) do-something-with(b);
}

My feeling is yes for the first example and "not so sure" for the second one, but could anyone give me some pointers/links to confirm that?

Schultz9999
  • 8,717
  • 8
  • 48
  • 87
  • 1
    Short Answer: Yes, the JVM will do these kinds of optimizations. (Provided that the function calls are not polymorphic and can be determined statically.) – Mysticial Oct 14 '11 at 19:56
  • 3
    @Mysticial: what makes you think so? :) I did read this http://www.ibm.com/developerworks/java/library/j-benchmark1/index.html but I am not positive I have answers. – Schultz9999 Oct 14 '11 at 19:57
  • I never said it will *always* do the optimizations. But that link you gave is a good read. – Mysticial Oct 14 '11 at 20:01
  • A method is really just a pointer; even if it isn't inlined, the JVM should have practically zero cost to invoke it. I can't say for sure whether they are inlined or not but I can tell you that enough metadeta about the original code structure stays intact to support stack traces. – Jesse Webb Oct 14 '11 at 20:39
  • 1
    @JesseWebb: zero cost still involves memory on stack. With sufficiently large number of calls, it may become costly. – Schultz9999 Oct 14 '11 at 22:41
  • Mandatory comment: if you have `some-complicated-string computation` in there, inlining will not buy you anything. It's only worth doing on routines that do practically nothing and that consume a large fraction of time. – Mike Dunlavey Oct 15 '11 at 00:16
  • 1
    @JesseWebb, The cost is non-zero http://stackoverflow.com/q/23584014/632951 . And the difference between zero and non-zero is that zero multiplied by 2^64 is **still** zero, while non-zero multiplied by 2^64 is a *huge* number. – Pacerier Sep 16 '14 at 08:16

6 Answers6

30

javac will present bytecode that is a faithful representation of the original Java program that generated the bytecode (except in certain situations when it can optimize: constant folding and dead-code elimination). However, optimization may be performed by the JVM when it uses the JIT compiler.

For the first scenario it looks like the JVM supports inlining (see under Methods here and see here for an inlining example on the JVM).

I couldn't find any examples of method inlining being performed by javac itself. I tried compiling a few sample programs (similar to the one you have described in your question) and none of them seemed to directly inline the method even when it was final. It would seem that these kind of optimizations are done by the JVM's JIT compiler and not by javac. The "compiler" mentioned under Methods here seems to be the HotSpot JVM's JIT compiler and not javac.

From what I can see, javac supports dead-code elimination (see the example for the second case) and constant folding. In constant folding, the compiler will precalculate constant expressions and use the calculated value instead of performing the calculation during runtime. For example:

public class ConstantFolding {

   private static final int a = 100;
   private static final int b = 200;

   public final void baz() {
      int c = a + b;
   }
}

compiles to the following bytecode:

Compiled from "ConstantFolding.java"
public class ConstantFolding extends java.lang.Object{
private static final int a;

private static final int b;

public ConstantFolding();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public final void baz();
  Code:
   0:   sipush  300
   3:   istore_1
   4:   return

}

Note that the bytecode has an sipush 300 instead of aload's getfields and an iadd. 300 is the calculated value. This is also the case for private final variables. If a and b were not static, the resulting bytecode will be:

Compiled from "ConstantFolding.java"
public class ConstantFolding extends java.lang.Object{
private final int a;

private final int b;

public ConstantFolding();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   bipush  100
   7:   putfield    #2; //Field a:I
   10:  aload_0
   11:  sipush  200
   14:  putfield    #3; //Field b:I
   17:  return

public final void baz();
  Code:
   0:   sipush  300
   3:   istore_1
   4:   return

}

Here also, an sipush 300 is used.

For the second case (dead-code elimination), I used the following test program:

public class InlineTest {

   private static final boolean debug = false;

   private void baz() {
      if(debug) {
         String a = foo();
      }
   }

   private String foo() {
      return bar();
   }

   private String bar() {
      return "abc";
   }
}

which gives the following bytecode:

Compiled from "InlineTest.java"
public class InlineTest extends java.lang.Object{
private static final boolean debug;

public InlineTest();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

private void baz();
  Code:
   0:   return

private java.lang.String foo();
  Code:
   0:   aload_0
   1:   invokespecial   #2; //Method bar:()Ljava/lang/String;
   4:   areturn

private java.lang.String bar();
  Code:
   0:   ldc #3; //String abc
   2:   areturn

}

As you can see, the foo is not called at all in baz because the code inside the if block is effectively "dead".

Sun's (now Oracle's) HotSpot JVM combines interpretation of the bytecode as well as JIT compilation. When bytecode is presented to the JVM the code is initially interpreted, but the JVM will monitor the bytecode and pick out parts that are frequently executed. It coverts these parts into native code so that they will run faster. For piece of bytecode that are not used so frequently, this compilation is not done. This is just as well because compilation has some overhead. So it's really a question of tradeoff. If you decide to compile all bytecode to nativecode, then the code can have a very long start-up delay.

In addition to monitoring the bytecode, the JVM can also perform static analysis of the bytecode as it is interpreting and loading it to perform further optimization.

If you want to know the specific kinds of optimizations that the JVM performs, this page at Oracle is pretty helpful. It describes the performance techniques used in the HotSpot JVM.

Vivin Paliath
  • 94,126
  • 40
  • 223
  • 295
  • and if the functions and `debug` are `final`? – ratchet freak Oct 14 '11 at 20:12
  • @ratchetfreak Oops that's a good point. I totally forgot about that. If they are final then `javac` optimizes out the call. Will edit my answer. Thanks for pointing that out. – Vivin Paliath Oct 14 '11 at 20:19
  • and whats the effect of making `bar` final? – ratchet freak Oct 14 '11 at 20:40
  • 2
    @ratchetfreak no effect apparently. `final` has no bearing on whether the method will be inlined or not, and marking a method as `final` doesn't necessarily mean that it will be inlined. Check out [this](http://www.ibm.com/developerworks/java/library/j-jtp1029/index.html) article by Brian Goetz. – Vivin Paliath Oct 14 '11 at 20:44
  • @VivinPaliath: was really looking for jitted code :) Thanks! Good answer. – Schultz9999 Oct 14 '11 at 22:38
  • 3
    Javac MUST NOT inline functions because this would break stack traces in Java, e.g. we wouldn't know any more in which method something is executed (not just bad for debugging, but the security concept in Java relies on knowing in which function we are and walking the stack). The JIT stores additional information to be able to correct this when doing inlining. About the JVM: Inlining is basically the most important optimization because it enables a gigantic number of other optimizations that would be way too complicated to do otherwise. Hence the JIT usually inlines agressively. – Voo Oct 15 '11 at 00:05
  • Since I had to look up the correct part of the spec anyhow because of another answer, I thought I'd quote it here as well, since it is rather important: `We note that a compiler cannot expand a method inline at compile time.` 3rd JSL `13.4.22 Method and Constructor Body`. It goes on and explains why we can't even do this for final methods. Reflection, correct stack traces and also compiling only parts of a program at once make inlining at compile time basically impossible. – Voo Oct 15 '11 at 00:16
  • @Voo thanks for expanding on that. I should have mentioned why the Java compiler cannot inline when compiling to bytecode. – Vivin Paliath Oct 15 '11 at 17:04
  • javac does some primitive inlining in some cases. Hardly worth mentioning, though, and it may have been "dialed back" since it was first introduced in 1.2. – Hot Licks Oct 17 '11 at 17:39
  • @VivinPaliath, +1, but your link http://wikis.sun.com/display/HotSpotInternals/PerformanceTechniques is down.... – Pacerier Sep 16 '14 at 08:42
  • The link about inline http://java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html is gone,would you please update it? Thanks in advance. – lin Apr 20 '17 at 02:11
  • @ChaojunZhong I've updated all links to wayback machine links. – Vivin Paliath Apr 20 '17 at 21:31
4

The JVM will most likely inline. In general it's best to optimize for human readability. Let the JVM do the runtime optimization.

JVM expert Brian Goetz says final has no impact on methods being inlined.

Steve Kuo
  • 61,876
  • 75
  • 195
  • 257
  • Upon condition `x`, either it is inlined or it is not inlined. There is no inbetween, does your "most likely" mean "yes, it is inlined" or "no, it is not inlined"? – Pacerier Sep 16 '14 at 08:44
  • @Pacerier in the Java runtime, the "condition x" is sufficiently complicated that "most likely" is a better guide than trying to describe the condition. If the routine isn't run many times, it will be interpreted without optimisation. Only after some number of invocations will it be JIT-compiled to native code, and potentially optimised with steps such as inlining. – slim Nov 29 '17 at 10:03
2

in the same class file the javac will be able to inline static and final (other class files might change the inlined function)

however the JIT will be able to optimize much more (including inlining superfluous removing bounds- and null checks, etc.) because it knows more about the code

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
  • 2
    Are you sure? As I wrote in another comment, as I understand it javac isn't allowed to inline functions because it would hide in which function something is executed (i.e. we'd suddenly get extremely strange locations in exceptions and more important the whole security concept in java would be broken). Hence the JLS forbids the compiler from inlining functions and the JIT has to save the necessary information if someone wants to walk the stack, etc. Since the only result of javac is the bytecode I don't see how we'd store the additional information in that case? – Voo Oct 15 '11 at 00:09
  • 1
    Just looked it up and I was indeed correct: `We note that a compiler cannot expand a method inline at compile time.` 3rd JLS `13.4.22 Method and Constructor Body` – Voo Oct 15 '11 at 00:13
  • @Voo they could have added something like `invokeinline` and `returninline` operations to mimic function calls when it's actually been inlined. but adding 2 operations just to avoid the function overhead probably isn't worth it – ratchet freak Oct 15 '11 at 00:22
  • Yeah I assume they could, but since they don't it's rather problematic. On further thought I think the first sentence would have to include at least that the inlined function doesn't call any function that has to do stack tracing (so we can't leave the compile unit). In practice I'd think this means the only thing that could be inlined would be final leaf functions in another function of the same compile unit. And then I'm not sure if I've considered everything. – Voo Oct 15 '11 at 00:29
2

A "highly optimizing" JIT compiler will inline both cases (and, @Mysticial, it might even inline some polymorphic cases, by employing various forms of trickery).

You can increase the chances of inlining by making methods final, and a few other tricks.

javac does some primitive inlining, mostly of final/private methods, primarily intended to help out some conditional compilation paradigms.

Hot Licks
  • 47,103
  • 17
  • 93
  • 151
  • With the Sun JVM, there's a tuneable limit on the (bytecode) size of method that will be inlined. – Donal Fellows Oct 14 '11 at 20:11
  • Yep, there are various knobs, internal and external. There will generally be a limit on how much total "bloat" will be allowed from inlining, etc. The externally visible knobs are apt to be "advisory", though -- not hard values one way or the other. – Hot Licks Oct 14 '11 at 20:16
0

If you thrown an exception in bar() and print the stacktrace you'll see the whole path of calls... I think java honor all of them.

The second case is the same, debug is just a variable of your system, not a define as in C++, so it is mandatory to evaluate it before.

yoprogramo
  • 1,306
  • 9
  • 14
-1

I might be wrong, but my feeling is "no in all cases". Because your string bar() can be overridden by overloaded by other classes in the same package. final methods are good candidates, but it depends on JIT.

Another interesting note is here.

Community
  • 1
  • 1
dma_k
  • 10,431
  • 16
  • 76
  • 128
  • 1
    Downvoting is easy, but writing a comment is difficult... For those, who do it, please leave a message, where I am wrong. – dma_k Oct 22 '11 at 11:38
  • Well, in most case JVM will auto detect does it is really final. Although you haven't append 'final'. If JVM noticed you're doing something that breaks JVM's assume. It will be de-optimized to ensure the correctlity. (It's implementation not specifications) – iseki Jan 01 '21 at 17:28
  • I will appreciate if you provide a link to a documentation describing how de-optimization is happening in JIT. Thanks in advance. – dma_k Jan 02 '21 at 00:16
  • Sorry, there're no documentation. I said it's implementation not specification. This is what a JVM developer said......emmm – iseki Jan 02 '21 at 02:42