Чи обман Java JIT під час запуску коду JDK?


405

Я орієнтував якийсь код, і я не міг змусити його працювати так швидко, як 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

і, здається, обидві версії генерують один і той же код. Тож хот-спот використовує деякі попередньо обчислені оптимізації, які я не можу використовувати у своєму коді? Я завжди розумів, що вони цього не роблять. Чим пояснюється ця різниця?


29
Цікаво. 1. Чи відповідає результат (або просто пощастило випадковим)? 2. Чи можете ви спробувати після розігріву JVM? 3. Чи можете ви усунути випадковий коефіцієнт і надати той самий набір даних, що і вхід для обох тестів?
Jigar Joshi

7
Ви спробували запустити свій бенчмарк із JMH openjdk.java.net/projects/code-tools/jmh ? Не так просто зробити вимірювання правильно вручну (розігріти і все таке).
Роман Пучковський

2
Так, це дуже послідовно. Якщо я дам йому працювати протягом 10 хвилин, я все одно отримаю таку ж різницю. Фіксоване випадкове насіння забезпечує, що обидва запуски отримують однаковий набір даних.
Koen Hendrikx

5
Ви, мабуть, все ще хочете JMH, про всяк випадок. І вам слід десь поставити свій модифікований BigInteger, щоб люди могли відтворити ваш тест і перевірити, чи працює ви, що ви думаєте, що ви працюєте.
пвг

Відповіді:


529

Так, HotSpot JVM є свого роду "обманом", оскільки він має спеціальну версію деяких BigIntegerметодів, яку ви не знайдете в коді Java. Ці методи називаються внутрішніми технологіями JVM .

Зокрема, BigInteger.multiplyToLenце instrinsic метод у HotSpot. У вихідній базі JVM існує спеціальна реалізована вручну схема монтажу , але лише для архітектури x86-64.

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

PS Ось список інших внутрішніх методів HotSpot.


141

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

Під ним той самий код для методу - лише дещо інша назва.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.