在优化期间,Java内联方法会吗?

2022-09-01 14:28:09

我想知道JVM / javac是否足够聪明,可以转向

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

string foo()
{
  return bar();
}

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

string a = bar();

或者在发布时剥离对 foo() 的不必要调用(因为无法访问的代码):

string a = foo(bar());

// bar is the same
...

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

我的感觉是,第一个例子是肯定的,第二个例子是“不太确定”,但任何人都可以给我一些指针/链接来证实这一点吗?


答案 1

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

对于第一个场景,看起来JVM支持内联(请参阅此处的方法,并请参阅此处以获取JVM上的内联示例)。

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

从我所看到的,支持死代码消除(参见第二种情况的示例)和持续折叠。在常量折叠中,编译器将预先计算常量表达式并使用计算值,而不是在运行时执行计算。例如:javac

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

}

请注意,字节码具有 一个 代替 's 和 一个 . 是计算值。变量也是如此。如果 和 不是静态的,则生成的字节码将为:sipush 300aloadgetfieldiadd300private finalab

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

}

这里也使用 an。sipush 300

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

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

}

如您所见,根本没有调用,因为块内的代码实际上是“死的”。foobazif

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

除了监视字节码之外,JVM 还可以在解释和加载字节码以执行进一步优化时对字节码执行静态分析。

如果您想知道JVM执行的特定类型的优化,Oracle的此页面非常有用。它描述了 HotSpot JVM 中使用的性能技术。


答案 2

JVM 很可能是内联的。通常,最好针对人类的可读性进行优化。让 JVM 执行运行时优化。

JVM专家Brian Goetz表示,对内联方法没有影响。final


推荐