为什么Java编译器不在单个if子句中内联这些调用?

fcy6dtqo  于 2023-01-19  发布在  Java
关注(0)|答案(3)|浏览(81)

我被拉入一个类似于以下代码的性能调查:

private void someMethod(String id) {
   boolean isHidden = someList.contains(id);
   boolean isDisabled = this.checkIfDisabled(id);

   if (isHidden && isDisabled) {
     // Do something here
   }
}

当我开始调查它的时候,我希望编译后的版本看起来像这样:

private void someMethod(String id) {
   if (someList.contains(id) && this.checkIfDisabled(id)) {
     // Do something here
   }
}

然而,令我惊讶的是,编译后的版本看起来与第一个版本完全一样,只是使用了局部变量,这导致isDisabled中的方法总是被调用,这就是性能问题所在。
我的解决方案是自己内联它,所以该方法现在在isHidden处短路,但它让我感到疑惑:为什么Java编译器在这种情况下不够聪明,不能为我内联这些调用?它真的需要有本地变量吗?
谢谢你:)

thtygnil

thtygnil1#

首先:java编译器(javac)几乎不做任何优化,这项工作几乎完全由JVM自己在运行时完成。
第二:只有当优化代码与未优化代码的行为没有明显差异时,才能进行这样的优化。
因为我们不知道(编译器大概也不知道)checkIfDisabled是否有任何可观察到的副作用,所以它必须假设它可能有,因此即使知道不需要该方法的返回值,对该方法的调用也不能被优化掉。
然而,对于要在运行时进行的这种优化,存在 * 选项 *:如果checkIfDisabled方法的主体(或多个主体,由于多态性)足够简单,那么 runtime很可能**实际上可以优化掉这些代码,如果它认识到调用永远不会有副作用(但我不知道是否有JVM实际上做了这种特定类型的优化)。
但是这种优化只有在明确了解checkIfDisabled的功能时才有可能,而且由于Java的动态类加载特性,这基本上意味着它几乎从不在编译时加载。
一般来说,虽然一些小的优化 * 可以 * 在编译时完成,但在运行时可能的优化范围要大得多(由于关于可用代码的信息量大大增加),因此Java设计人员决定将基本上所有的优化工作放在系统的运行时部分。

50few1ms

50few1ms2#

这个问题最明显的解决方案就是重写代码如下:

if (someList.contains(id)) {
  if (this.checkIfDisabled(id)) {
    // do something here

如果,在你对问题的“人类”估计中,一个测试可能意味着另一个测试根本不需要执行,那么简单地“这样写"。

daolsyd0

daolsyd03#

Java编译器的优化是很棘手的。大多数优化都是由JIT编译器在运行时完成的。有几个级别的优化,默认情况下,最大数量的优化将在5000次方法调用后进行。但是,要查看应用了哪些优化是相当困难的,因为JIT将代码直接编译到平台的本机代码中

相关问题