优化期间会使用 Java 内联方法吗?

2022-01-16 00:00:00 optimization jvm java javac

不知道JVM/javac是不是聪明到可以转

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;
}

进入

string a = bar();

或者在发布情况下去掉对 foo() 的不必要调用(因为无法访问代码):

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?

推荐答案

javac 将呈现字节码,该字节码是生成字节码的原始 Java 程序的忠实表示(除非在某些情况下它可以优化:常量折叠和死代码消除).但是,JVM 在使用 JIT 编译器时可能会执行优化.

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.

对于第一种情况,JVM 似乎支持内联(参见 方法 这里 并查看 这里查看 JVM 上的内联示例).

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

我找不到任何由 javac 本身执行的方法内联示例.我尝试编译一些示例程序(类似于您在问题中描述的那个),但即使是 final,它们似乎都没有直接内联该方法.这些优化似乎是由 JVM 的 JIT 编译器完成的,而不是由 javac 完成的.Methods here 似乎是 HotSpot JVM 的 JIT 编译器,而不是 javac.

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.

据我所知,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;
   }
}

编译成以下字节码:

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

}

注意字节码有一个 sipush 300 而不是 aloadgetfields 和一个 iadd.300 是计算出来的值.private final 变量也是如此.如果 ab 不是静态的,则生成的字节码将是:

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

}

这里也使用了 sipush 300.

对于第二种情况(死代码消除),我使用了以下测试程序:

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";
   }
}

给出以下字节码:

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

}

如您所见,在 baz 中根本没有调用 foo,因为 if 块内的代码实际上是死的".

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

Sun(现在是 Oracle)的 HotSpot JVM 结合了字节码解释和 JIT 编译.当字节码呈现给 JVM 时,代码最初会被解释,但 JVM 会监视字节码并挑选出经常执行的部分.它将这些部分转换为本机代码,以便它们运行得更快.对于不经常使用的一段字节码,不进行此编译.这也很好,因为编译有一些开销.所以这真的是一个权衡的问题.如果你决定将所有字节码都编译为nativecode,那么代码可能会有很长的启动延迟.

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.

除了监控字节码,JVM 还可以在解释和加载字节码时对字节码进行静态分析,以进行进一步的优化.

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.

如果您想了解 JVM 执行的具体优化类型,this page 非常有帮助.它描述了 HotSpot JVM 中使用的性能技术.

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.

相关文章