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