运行 JDK 代码时 Java JIT 会作弊吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45912510/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 08:59:08  来源:igfitidea点击:

Does Java JIT cheat when running JDK code?

javajvmjitjvm-hotspot

提问by Koen Hendrikx

I was benchmarking some code, and I could not get it to run as fast as with java.math.BigInteger, even when using the exact same algorithm. So I copied java.math.BigIntegersource into my own package and tried this:

我正在对一些代码进行基准测试java.math.BigInteger,即使使用完全相同的算法,我也无法让它运行得像 一样快。所以我将java.math.BigInteger源代码复制到我自己的包中并尝试了这个:

//import java.math.BigInteger;

public class MultiplyTest {
    public static void main(String[] args) {
        Random r = new Random(1);
        long tm = 0, count = 0,result=0;
        for (int i = 0; i < 400000; i++) {
            int s1 = 400, s2 = 400;
            BigInteger a = new BigInteger(s1 * 8, r), b = new BigInteger(s2 * 8, r);
            long tm1 = System.nanoTime();
            BigInteger c = a.multiply(b);
            if (i > 100000) {
                tm += System.nanoTime() - tm1;
                count++;
            }
            result+=c.bitLength();
        }
        System.out.println((tm / count) + "nsec/mul");
        System.out.println(result); 
    }
}

When I run this (jdk 1.8.0_144-b01 on MacOS) it outputs:

当我运行这个(MacOS 上的 jdk 1.8.0_144-b01)时,它输出:

12089nsec/mul
2559044166

When I run it with the import line uncommented:

当我在未注释的导入行的情况下运行它时:

4098nsec/mul
2559044166

It's almost three times as fast when using the JDK version of BigInteger versus my version, even if it's using the exact same code.

使用 BigInteger 的 JDK 版本与我的版本相比,速度几乎是我的三倍,即使它使用完全相同的代码。

I've examined the bytecode with javap, and compared compiler output when running with options:

我已经使用 javap 检查了字节码,并在使用选项运行时比较了编译器输出:

-Xbatch -XX:-TieredCompilation -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions 
-XX:+PrintInlining -XX:CICompilerCount=1

and both versions seem to generate the same code. So is hotspot using some precomputed optimisations that I can't use in my code? I always understood that they don't. What explains this difference?

并且两个版本似乎都生成了相同的代码。那么热点是否使用了一些我无法在我的代码中使用的预计算优化?我一直明白他们没有。什么解释了这种差异?

回答by apangin

Yes, HotSpot JVM is kind of "cheating", because it has a special version of some BigIntegermethods that you won't find in Java code. These methods are called JVM intrinsics.

是的,HotSpot JVM 有点“作弊”,因为它有一些BigInteger在 Java 代码中找不到的特殊方法。这些方法称为JVM 内部函数

In particular, BigInteger.multiplyToLenis an instrinsic method in HotSpot. There is a special hand-coded assembly implementationin JVM source base, but only for x86-64 architecture.

特别是,BigInteger.multiplyToLen是 HotSpot 中的一个内在方法。JVM 源代码库中有一个特殊的手工编码程序集实现,但仅适用于 x86-64 架构。

You may disable this instrinsic with -XX:-UseMultiplyToLenIntrinsicoption to force JVM to use pure Java implementation. In this case the performance will be similar to the performance of your copied code.

您可以通过-XX:-UseMultiplyToLenIntrinsic选项禁用此内在函数以强制 JVM 使用纯 Java 实现。在这种情况下,性能将类似于您复制的代码的性能。

P.S.Here is a listof other HotSpot intrinsic methods.

PS这是其他 HotSpot 内在方法的列表

回答by Eugene

In Java 8this is indeed an intrinsic method; a slightly modified version of the method:

Java 8 中,这确实是一个内在方法;该方法的一个稍微修改的版本:

 private static BigInteger test() {

    Random r = new Random(1);
    BigInteger c = null;
    for (int i = 0; i < 400000; i++) {
        int s1 = 400, s2 = 400;
        BigInteger a = new BigInteger(s1 * 8, r), b = new BigInteger(s2 * 8, r);
        c = a.multiply(b);
    }
    return c;
}

Running this with:

运行这个:

 java -XX:+UnlockDiagnosticVMOptions  
      -XX:+PrintInlining 
      -XX:+PrintIntrinsics 
      -XX:CICompilerCount=2 
      -XX:+PrintCompilation   
       <YourClassName>

This will print lots of lines and one of them will be:

这将打印很多行,其中之一将是:

 java.math.BigInteger::multiplyToLen (216 bytes)   (intrinsic)

In Java 9on the other hand that method seems to not be an intrinsic anymore, but in turn it calls a method that is an intrinsic:

另一方面,在Java 9 中,该方法似乎不再是内在的,但反过来它调用了一个内在的方法:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

So running the same code under Java 9 (with the same parameters) will reveal:

因此,在 Java 9 下运行相同的代码(使用相同的参数)将显示:

java.math.BigInteger::implMultiplyToLen (216 bytes)   (intrinsic)

Underneath it's the same code for the method - just a slightly different naming.

下面是该方法的相同代码 - 只是命名略有不同。