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

时间:2022-04-21 17:22:52

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.BigInteger source into my own package and tried this:

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

//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.

使用JDK版本的BigInteger比使用我的版本快近三倍,即使它使用的是完全相同的代码。

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?

两个版本似乎都生成了相同的代码。那么hotspot是否使用了一些我在代码中不能使用的预先计算的优化呢?我一直知道他们没有。如何解释这种差异呢?

2 个解决方案

#1


493  

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

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

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

先导入BigInteger。特别是,多氯联苯是目前热点研究的新方法。JVM源代码库中有一个特殊的手工编写的程序集实现,但只适用于x86-64架构。

You may disable this instrinsic with -XX:-UseMultiplyToLenIntrinsic option 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 list of other HotSpot intrinsic methods.

这里是一些其他热点的内在方法。

#2


124  

In Java 8 this is indeed an intrinsic, 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 9 on 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.

在它的下面是相同的方法代码——只是命名稍有不同。

#1


493  

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

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

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

先导入BigInteger。特别是,多氯联苯是目前热点研究的新方法。JVM源代码库中有一个特殊的手工编写的程序集实现,但只适用于x86-64架构。

You may disable this instrinsic with -XX:-UseMultiplyToLenIntrinsic option 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 list of other HotSpot intrinsic methods.

这里是一些其他热点的内在方法。

#2


124  

In Java 8 this is indeed an intrinsic, 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 9 on 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.

在它的下面是相同的方法代码——只是命名稍有不同。