【问题标题】:When JVM does not execute method for code optimization当JVM不执行代码优化方法时
【发布时间】:2015-04-17 06:49:11
【问题描述】:

我正在阅读 Scott Oaks 的《Java Performance》一书,遇到了一段代码,据说 Java 7 或 8 JVM 足够聪明,可以跳过 for 循环中提供的计算部分,因为将来不会使用结果(微基准测试) .

书中提到的代码:

public void doTest() {
    // Main Loop
    double l;
    long then = System.currentTimeMillis();

    for (int i = 0; i < nLoops; i++) {
        l = fibImpl1(50);
    }
    long now = System.currentTimeMillis();
    System.out.println("Elapsed time: " + (now - then));
}

private double fibImpl1(int n) {
   if (n < 0) throw new IllegalArgumentException("Must be > 0");
   if (n == 0) return 0d;
   if (n == 1) return 1d;
   double d = fibImpl1(n - 2) + fibImpl(n - 1);
   if (Double.isInfinite(d)) throw new ArithmeticException("Overflow");
  return d;
} 

书中的进一步陈述: 因为从不使用斐波那契计算的结果,所以编译器可以随意丢弃该计算。智能编译器(包括当前的 Java 7 和 8 编译器)最终会执行此代码:

long then = System.currentTimeMillis();
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));

为了验证相同,我尝试了一个代码,但经过时间计算并没有反映上面解释的理论。

我的代码版本:

    public class APSum {

    public static void main(String[] args) {

        long then = System.currentTimeMillis();
        ArithmeticProgression.sum(500000000);
        long now = System.currentTimeMillis();
        System.out.println("Elapsed time: " + (now - then));
    }
}

    class ArithmeticProgression{
    public static double sum(int i){
        double sum=0;
        for(int index=0; index<=i; index++){
            sum = sum + (double)index;
        }
        return sum;
    }
}

所以请让我知道如何实现书中提到的场景。还是JVM希望JVM是否要优化调用?

【问题讨论】:

  • 你尝试过书中的代码并成功了吗?
  • 编译器是聪明的,但不是无限聪明的。因为结果没有被使用,它可以走多远看看它是否可以扔掉一些东西是有限制的。您的示例可能太复杂了,因此编译器不会将其丢弃。
  • 你在混淆概念。据我所知,将源代码转换为字节码(类文件)的 java compiler 非常愚蠢。与 gcc 等其他编译器相比,它没有进行任何重大优化。换句话说:在编译器构造函数知道要优化的许多概念中;只有少数被应用。如果 Java7、8 改变了这种情况;我一定错过了 ... 。但是发生的情况是 just-in-time 编译器会在 运行 时不断优化代码;例如,请参见此处docs.oracle.com/cd/E15289_01/doc.40/e15058/underst_jit.htm
  • JIT 优化也高度依赖于平台。因此,不要期望相同代码在不同平台上的行为一致。您可能正在查看称为escape analysis专业 JVM 优化
  • 执行优化的不是编译器,而是 JIT。 Javac 对于编译器来说是最愚蠢的。

标签: java jvm


【解决方案1】:

现代 JVM 太复杂了,做各种优化。如果您尝试测量一小段代码,那么在没有非常非常详细了解 JVM 正在做什么的情况下正确执行它真的很复杂。死代码消除 (DCE) 是一种经常导致微基准测试出错的优化。

有两个经典错误(阅读更多常见错误http://shipilev.net/talks/jvmls-July2014-benchmarking.pdfhttps://stackoverflow.com/a/513259/1352098):

  • 由于最小的编译单元是方法,基准测试必须有多个主要方法。
  • 没有预热迭代。始终包含一个预热阶段,该阶段会一直运行您的测试内核,足以在计时阶段之前触发所有初始化和编译。

校正后我们的基准如下所示:

    public class APSum {

    public static void main(String[] args) {
        for (int i = 0; i < 5000; i++) {
            test();
        }
    }

    private static void test() {
        long then = System.currentTimeMillis();
        sum(5000000);
        long now = System.currentTimeMillis();
        System.out.println("Elapsed time: " + (now - then));
    }

    public static double sum(int i){
        double sum=0;
        for(int index=0; index<=i; index++){
            sum = sum + (double)index;
        }
        return sum;
    }
    }

在本例中,DCE 仅在内联之后发生。让我们先来看看内联树(-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:-BackgroundCompilation 可用)

   @ 0   java.lang.System::currentTimeMillis (0 bytes)   intrinsic
   @ 6   edu.jvm.runtime.APSum::sum (22 bytes)   inlining prohibited by policy
   @ 10   java.lang.System::currentTimeMillis (0 bytes)   intrinsic

由于OSR,编译器未能内联方法APSum::sum。事实上,尽管OSR compilation 在基准测试中经常被触发(尤其是在微基准测试中),但在应用程序代码中触发的频率却较低。为了获得正确的结果,我们必须添加更多的预热迭代:

    public class APSum {

    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            test();
        }
    }

    private static void test() {
        long then = System.currentTimeMillis();
        sum(5000000);
        long now = System.currentTimeMillis();
        System.out.println("Elapsed time: " + (now - then));
    }

    public static double sum(int i){
        double sum=0;
        for(int index=0; index<=i; index++){
            sum = sum + (double)index;
        }
        return sum;
    }
    }

因此,在迭代结束时,我们有:

  Elapsed time: 5
  Elapsed time: 4
  Elapsed time: 5

    @ 0   java.lang.System::currentTimeMillis (0 bytes)   (intrinsic)
    @ 6   edu.jvm.runtime.APSum::sum (22 bytes)   inline (hot)
    @ 10   java.lang.System::currentTimeMillis (0 bytes)   (intrinsic)

  Elapsed time: 0
  Elapsed time: 0
  Elapsed time: 0

【讨论】:

猜你喜欢
  • 2018-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多