Является ли JIT-чит JIT при запуске JDK-кода?

Я сравнивал некоторый код, и я не мог заставить его работать так же быстро, как с 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); 
    }
}

Когда я запускаю это (jdk 1.8.0_144-b01 на MacOS), он выдает:

12089nsec/mul
2559044166

Когда я запускаю его с недопустимой линией импорта:

4098nsec/mul
2559044166

Это почти в три раза быстрее при использовании JDK-версии BigInteger по сравнению с моей версией, даже если она использует тот же самый код.

Я изучил байт-код с помощью javap и сравнивал вывод компилятора при работе с параметрами:

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

и обе версии генерируют один и тот же код. То есть точка доступа использует некоторые предварительные вычисления, которые я не могу использовать в своем коде? Я всегда понимал, что они этого не делают. Чем объясняется эта разница?

Ответ 1

Да, HotSpot JVM - это своего рода "обман", потому что у него есть специальная версия некоторых методов BigInteger, которые вы не найдете в Java-коде. Эти методы называются JVM intrinsics.

В частности, BigInteger.multiplyToLen является инстримным методом в HotSpot. В исходной базе JVM существует специальная ручная сборка сборки, но только для архитектуры x86-64.

Вы можете отключить этот instrinsic с опцией -XX:-UseMultiplyToLenIntrinsic, чтобы заставить JVM использовать чистую реализацию Java. В этом случае производительность будет аналогична производительности вашего скопированного кода.

P.S. Вот список других встроенных методов HotSpot.

Ответ 2

В 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)

Под ним один и тот же код для метода - просто немного другое имя.