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

问题描述:

我正在对一些代码进行基准测试,即使使用完全相同的算法,我也无法使其运行速度与java.math.BigInteger一样快。 所以我复制java.math.BigInteger源到我自己的包,并试图此:运行JDK代码时,Java JIT会作弊吗?

//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); 
    } 
} 

当我运行这个(在Mac OS JDK 1.8.0_144-B01)则输出:

12089nsec/mul 
2559044166 

当我运行导入行注释掉:

4098nsec/mul 
2559044166 

它几乎*倍使用BigInteger的JDK版本与我的版本时,即使它使用完全相同的码。

的选项运行时,我已经审查了javap的字节码,并与编译器输出:

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

和两个版本似乎产生相同的代码。 那么,热点使用一些预先计算的优化,我不能在我的代码中使用?我一直都明白他们没有。 这个区别是什么解释?

+25

有趣。 1.结果是否一致(或只是幸运的随机)? 2.您可以尝试升温JVM之后吗? 3.您是否可以消除随机因素,并提供相同的数据集作为测试的输入? –

+7

您是否尝试使用JMH运行您的基准测试http://openjdk.java.net/projects/code-tools/jmh/?手动进行测量并不是那么容易(预热和所有这些)。 –

+2

是的,它非常一致。如果我让它运行10分钟,我仍然会得到同样的区别。固定的随机种子确保两次运行获得相同的数据集。 –

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

特别是,BigInteger.multiplyToLen是HotSpot中的内在方法。 JVM源代码中有一个特殊的hand-coded assembly implementation,但只适用于x86-64体系结构。

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

P.S.这是其他HotSpot内在方法的list

的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; 
} 

与运行此:

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

这将打印大量线路和一个他们将是:

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

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

@HotSpotIntrinsicCandidate 
private static int[] implMultiplyToLen 

所以运行Java 9在相同的代码(用同样的参数)将显示:

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

下面是该方法相同的代码 - 只是一个稍微不同的命名。