优化期间 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);
}
对于第一个示例,我的感觉是肯定的,而对于第二个示例,我的感觉是“不太确定”,但可以有人给我一些指示/链接来确认这一点吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
javac
将呈现字节码,该字节码是生成字节码的原始 Java 程序的忠实表示(除非在某些可以优化的情况下:不断折叠和死机)代码消除)。然而,当 JVM 使用 JIT 编译器时,可能会执行优化。对于第一个场景,JVM 似乎支持内联(请参阅方法 此处 并查看 此处有关 JVM 上的内联示例)。
我找不到任何由
javac
本身执行方法内联的示例。我尝试编译一些示例程序(类似于您在问题中描述的程序),但它们似乎都没有直接内联该方法,即使它是最终的。看起来这些类型的优化是由 JVM 的 JIT 编译器完成的,而不是由javac
完成的。 方法下提到的“编译器”这里似乎是HotSpot JVM的JIT编译器,而不是javac
。据我所知,
javac
支持死代码消除(请参阅第二种情况的示例)和常量折叠。在常量折叠中,编译器将预先计算常量表达式并使用计算出的值,而不是在运行时执行计算。例如:编译为以下字节码:
请注意,该字节码具有
sipush 300
而不是aload
的getfield
和一个iadd
。300
是计算值。private Final
变量也是如此。如果a
和b
不是静态的,则生成的字节码将为:这里也使用
sipush 300
。对于第二种情况(死代码消除),我使用了以下测试程序:
它给出了以下字节码:
如您所见,
foo
在baz 因为
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 wasfinal
. It would seem that these kind of optimizations are done by the JVM's JIT compiler and not byjavac
. The "compiler" mentioned under Methods here seems to be the HotSpot JVM's JIT compiler and notjavac
.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:compiles to the following bytecode:
Note that the bytecode has an
sipush 300
instead ofaload
'sgetfield
s and aniadd
.300
is the calculated value. This is also the case forprivate final
variables. Ifa
andb
were not static, the resulting bytecode will be:Here also, an
sipush 300
is used.For the second case (dead-code elimination), I used the following test program:
which gives the following bytecode:
As you can see, the
foo
is not called at all inbaz
because the code inside theif
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.
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.在同一个类文件中,javac 将能够内联
static
和final
(其他类文件可能会更改内联函数),但是 JIT 将能够优化更多(包括内联多余的删除边界和空检查等),因为它了解更多关于代码的信息
in the same class file the javac will be able to inline
static
andfinal
(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
“高度优化”的 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.
如果你在 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.
我可能是错的,但我的感觉是“在所有情况下都不会”。因为您的
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.