在优化过程中Java内联方法会不会?
我想知道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); }
对于第一个例子我的感觉是肯定的,对于第二个例子我的感觉是“不太确定”,但是有人可以给我一些指示/链接来确认吗?
javac
将呈现字节码,它是生成字节码的原始Java程序的忠实表示(除非在某些情况下可以优化: 常量折叠和死代码消除 )。 但是,JVM在使用JIT编译器时可以执行优化。
对于第一个场景,看起来JVM支持内联(请参阅此处的 方法 ,并在此处查看JVM上的内联示例)。
我找不到任何由javac
本身执行的方法内联的示例。 我尝试编译一些示例程序(类似于你在问题中描述的程序)并且它们似乎都没有直接内联方法,即使它是final
。 似乎这些优化是由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."":()V 4: return public final void baz(); Code: 0: sipush 300 3: istore_1 4: return }
请注意,字节码有一个sipush 300
而不是aload
的getfield
和iadd
。 300
是计算值。 private final
变量也是如此。 如果a
和b
不是静态的,则生成的字节码将是:
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."":()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."":()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
块中的代码实际上是“死”。
Sun(现在的Oracle)HotSpot JVM结合了字节码的解释以及JIT编译。 当字节码呈现给JVM时,代码最初被解释,但JVM将监视字节码并挑选出经常执行的部分。 它将这些部分转换为本机代码,以便它们运行得更快。 对于那些频繁使用的字节码,此编译未完成。 这也是因为编译有一些开销。 所以这真的是一个权衡问题。 如果您决定将所有字节码编译为本机代码,则代码可能会有很长的启动延迟。
除了监视字节码之外,JVM还可以在解释和加载字节码时执行字节码的静态分析,以执行进一步的优化。
如果您想了解JVM执行的特定类型的优化,那么Oracle的这个页面非常有用。 它描述了HotSpot JVM中使用的性能技术。
在同一个类文件中,javac将能够内联static
和final
(其他类文件可能会更改内联函数)
然而,JIT将能够进行更多优化(包括内联多余的删除边界和空检查等),因为它更了解代码
“高度优化”的JIT编译器将内联两种情况(并且,@ Mysticial,甚至可能通过采用各种forms的欺骗来内联一些多态情况)。
你可以通过制作最终方法以及其他一些技巧来增加内联的几率。
javac做了一些原始的内联,主要是最终/私有方法,主要是为了帮助一些条件编译范例。
JVM最有可能内联。 一般来说,最好优化人类可读性。 让JVM进行运行时优化。
JVM专家Brian Goetz表示, final
对内联方法没有影响。
如果你在bar()中抛出exception并打印堆栈跟踪,你会看到整个调用路径……我认为java会尊重所有这些。
第二种情况是相同的,debug只是系统的变量,而不是C ++中的定义,因此必须先对其进行评估。
我可能错了,但我的感觉是“在所有情况下都没有”。 因为你的string bar()
可以被同一个包中的其他类重载所覆盖。 final
方法是很好的候选者,但它取决于JIT。
另一个有趣的注意事项是。