JIT-微观优化-if语句消除

时间:2018-11-13 00:06:40

标签: java jvm jit jvm-hotspot

假设我们有以下代码:

public static void check() {   
    if (Config.initialized) {
           ...
    }
}

Config.initialized在开始时为false,仅在该方法已经JIT编译后的某个时候才更改为true。该值永远不会返回false。

我“知道”正在进行许多非常复杂的优化(循环展开,分支预测,内联,转义分析等),尽管我距离详细地了解所有这些还很遥远,但我主要感兴趣在以下内容中:

  1. JIT编译器是否可以检测到某个时间点之后if始终为true,以便可以完全跳过检查?完全我真的是说没有变量访问,没有条件检查/ jne等……

  2. 如果JIT无法摆脱样本中的(从某个特定点开始)不必要的检查(我不知道怎么办),我可以做些什么来支持?我唯一的想法是在初始化事件发生后重新转换类并从字节码中删除不必要的代码。

我知道这是完全的微优化,即使使用JMH之类的工具也可能很难测量,但是我仍然想知道和理解。

最后但并非最不重要:

  1. 我的理解是否正确,如果在某些地方内联了上述方法,以便在发生某些更改时所有这些方法都将被重新编译(假设它们很热),从而需要重新编译check方法?

如果我正确理解了JitWatch测试的结果,则上述问题的答案应该是:

  1. 不行。始终会有条件检查。
  2. 仅通过转换

1 个答案:

答案 0 :(得分:6)

  
      
  1. JIT编译器是否可以检测到在特定点之后if始终为真
  2.   

是的,如果该字段为static final,并且在JIT编译器启动时已初始化其所有者类。显然,这种情况不适用于您的情况,因为无法创建Config.initialized {{ 1}}。

  
      
  1. 有什么我可以支持的吗?
  2.   

java.lang.invoke.MutableCallSite进行救援。

该课程专门用于完成您所要求的事情。其setTarget方法支持在运行时重新绑定呼叫站点。在后台,它会导致当前编译方法的不优化,并有可能在以后使用新目标重新编译它。

可以使用dynamicInvoker方法获得用于调用static final目标的MethodHandle。请注意,MutableCallSite应该为MethodHandle,以便允许内联。

  
      
  1. 如果上述方法内联,那么将重新编译所有这些方法
  2.   

是的

这里是一个基准测试,它证明了static final方法在开始时与mutableCallSite一样快,并且在切换切换后也与alwaysFalse一样快。我还提供了一个静态字段切换项,以供@Holger建议进行比较。

alwaysTrue

通过5次热身迭代和10次测量迭代运行基准测试,我得到以下结果:

package bench;

import org.openjdk.jmh.annotations.*;
import java.lang.invoke.*;
import java.util.concurrent.*;

@State(Scope.Benchmark)
public class Toggle {
    static boolean toggleField = false;

    static final MutableCallSite toggleCallSite =
            new MutableCallSite(MethodHandles.constant(boolean.class, false));

    static final MethodHandle toggleMH = toggleCallSite.dynamicInvoker();

    public void switchToggle() {
        toggleField = true;
        toggleCallSite.setTarget(MethodHandles.constant(boolean.class, true));
        MutableCallSite.syncAll(new MutableCallSite[]{toggleCallSite});
        System.out.print("*** Toggle switched *** ");
    }

    @Setup
    public void init() {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
        executor.schedule(this::switchToggle, 10100, TimeUnit.MILLISECONDS);
        executor.shutdown();
    }

    @Benchmark
    public int alwaysFalse() {
        return 0;
    }

    @Benchmark
    public int alwaysTrue() {
        return ThreadLocalRandom.current().nextInt();
    }

    @Benchmark
    public int field() {
        if (toggleField) {
            return ThreadLocalRandom.current().nextInt();
        } else {
            return 0;
        }
    }

    @Benchmark
    public int mutableCallSite() throws Throwable {
        if ((boolean) toggleMH.invokeExact()) {
            return ThreadLocalRandom.current().nextInt();
        } else {
            return 0;
        }
    }
}