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

发布于 2024-12-09 16:07:34 字数 490 浏览 0 评论 0 原文

我想知道 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);
}

对于第一个示例,我的感觉是肯定的,而对于第二个示例,我的感觉是“不太确定”,但可以有人给我一些指示/链接来确认这一点吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

辞取 2024-12-16 16:07:34

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

对于第一个场景,JVM 似乎支持内联(请参阅方法 此处 并查看 此处有关 JVM 上的内联示例)。

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

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

}

请注意,该字节码具有 sipush 300 而不是 aloadgetfield 和一个 iadd300 是计算值。 private Final 变量也是如此。如果 ab 不是静态的,则生成的字节码将为:

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

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

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

}

如您所见,foobaz 因为 if 块内的代码实际上是“死的”。

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

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

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

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.

无远思近则忧 2024-12-16 16:07:34

JVM 很可能会内联。一般来说,最好针对人类可读性进行优化。让 JVM 进行运行时优化。

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

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.

寒冷纷飞旳雪 2024-12-16 16:07:34

在同一个类文件中,javac 将能够内联 staticfinal (其他类文件可能会更改内联函数),

但是 JIT 将能够优化更多(包括内联多余的删除边界和空检查等),因为它了解更多关于代码的信息

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

浮华 2024-12-16 16:07:34

“高度优化”的 JIT 编译器将内联这两种情况(并且,@Mysticial,它甚至可能通过采用各种形式的欺骗来内联一些多态情况)。

您可以通过将方法设为最终方法以及其他一些技巧来增加内联的机会。

javac 做了一些原始内联,主要是最终/私有方法,主要是为了帮助一些条件编译范例。

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.

じее 2024-12-16 16:07:34

如果你在 bar() 中抛出异常并打印堆栈跟踪,你将看到调用的整个路径......我认为 java 尊重所有这些。

第二种情况是一样的,debug只是你系统的一个变量,而不是像C++中的定义,所以之前必须对其进行评估。

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.

葬花如无物 2024-12-16 16:07:34

我可能是错的,但我的感觉是“在所有情况下都不会”。因为您的 string bar() 可能会被同一包中的其他类重载而覆盖。 final 方法是不错的选择,但它取决于 JIT。

另一个有趣的注释是这里< /a>.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文