क्या JDK कोड चलाते समय Java JIT धोखा देती है?




jvm jvm-hotspot (2)

मैं कुछ कोड बेंच रहा था, और मैं इसे ठीक उसी एल्गोरिथ्म का उपयोग करते हुए भी 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); 
    }
}

जब मैं इसे चलाता हूं (MacOS पर jdk 1.8.0_144-b01) यह आउटपुट करता है:

12089nsec/mul
2559044166

जब मैं इसे आयात लाइन के साथ चलाता हूँ तो यह अधूरा है:

4098nsec/mul
2559044166

BigInteger बनाम मेरे संस्करण के JDK संस्करण का उपयोग करते समय यह लगभग तीन गुना तेज़ है, भले ही यह ठीक उसी कोड का उपयोग कर रहा हो।

मैंने javap के साथ bytecode की जांच की है, और विकल्प के साथ चलने पर संकलक आउटपुट की तुलना की है:

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

और दोनों संस्करण एक ही कोड उत्पन्न करते हैं। तो हॉटस्पॉट कुछ प्री-कॉम्पीटेड ऑप्टिमाइज़ेशन का उपयोग कर रहा है जो मैं अपने कोड में उपयोग नहीं कर सकता हूं? मुझे हमेशा समझ में आया कि वे नहीं करते। इस अंतर को क्या समझाता है?


हां, हॉटस्पॉट जेवीएम "धोखा" की तरह है, क्योंकि इसमें कुछ BigInteger तरीकों का एक विशेष संस्करण है जो आपको जावा कोड में नहीं मिलेगा। इन विधियों को जेवीएम इंट्रिनिक्स कहा जाता है।

विशेष रूप से, BigInteger.multiplyToLen हॉटस्पॉट में एक इंस्ट्रेंसिक विधि है। जेवीएम स्रोत आधार में एक विशेष हाथ-कोडित विधानसभा कार्यान्वयन है, लेकिन केवल x86-64 वास्तुकला के लिए।

आप इस इंस्ट्रेंसिक को -XX:-UseMultiplyToLenIntrinsic विकल्प के साथ JVM को शुद्ध जावा कार्यान्वयन का उपयोग करने के लिए मजबूर करने के लिए अक्षम कर सकते हैं। इस मामले में प्रदर्शन आपके कॉपी किए गए कोड के प्रदर्शन के समान होगा।

PS यहाँ अन्य हॉटस्पॉट आंतरिक तरीकों की एक list है।


जावा 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)

दूसरी ओर जावा 9 में यह विधि अब आंतरिक नहीं लगती है, लेकिन बदले में यह एक ऐसी विधि कहलाती है जो एक आंतरिक है:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

जावा 9 के तहत एक ही कोड चलाने (समान मापदंडों के साथ) से पता चलेगा:

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

नीचे यह विधि के लिए एक ही कोड है - बस थोड़ा अलग नामकरण।






jvm-hotspot