समकक्ष स्थिर और गैर स्थिर तरीकों की गति में बड़ा अंतर


86

इस कोड में जब मैं mainविधि में एक ऑब्जेक्ट बनाता हूं और फिर उस ऑब्जेक्ट विधि को कॉल करता ff.twentyDivCount(i)हूं : (16010 एमएस में चलता है), यह एनोटेशन का उपयोग करके इसे कॉल करने की तुलना में बहुत तेज चलता है: twentyDivCount(i)(59516 एमएस में चलता है)। बेशक, जब मैं इसे एक ऑब्जेक्ट बनाए बिना चलाता हूं, तो मैं विधि को स्थिर बनाता हूं, इसलिए इसे मुख्य में कहा जा सकता है।

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {    // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way
                       // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

EDIT: अब तक ऐसा लगता है कि विभिन्न मशीनें अलग-अलग परिणाम देती हैं, लेकिन JRE 1.8 का उपयोग करते हुए। जहां मूल परिणाम लगातार पुन: उत्पन्न होता है।


4
आप अपना बेंचमार्क कैसे चला रहे हैं? मैं शर्त लगाता हूं कि यह कोड को अनुकूलित करने के लिए पर्याप्त समय नहीं होने पर जेवीएम की एक कलाकृति है।
पैट्रिक कॉलिन्स

2
लगता है कि यह के JVM के लिए पर्याप्त समय संकलन और के रूप में मुख्य विधि के लिए एक OSR प्रदर्शन करने +PrintCompilation +PrintInliningसे पता चलता है
Tagir Valeev

1
मैंने कोड स्निपेट की कोशिश की थी, लेकिन मुझे ऐसा कोई समय अंतर नहीं मिला जैसा कि स्टैब्ज़ ने कहा। वे 56282ms (उदाहरण का उपयोग करते हुए) 54551ms (स्थिर विधि के रूप में) हैं।
डॉन चक्कप्पन

1
@PatrickCollins पाँच सेकंड पर्याप्त होना चाहिए। मैंने इसे थोड़ा फिर से लिखा है ताकि आप दोनों को माप सकें (एक जेवीएम प्रति संस्करण शुरू हो जाता है)। मुझे पता है कि एक बेंचमार्क के रूप में यह अभी भी त्रुटिपूर्ण है, लेकिन यह पर्याप्त रूप से आश्वस्त करता है: 1457 एमएस स्टेटिक बनाम 5312 एमएस नॉन_एसटीएटीआईसी।
माआर्टिनस

1
प्रश्न के बारे में अभी तक विस्तार से जांच नहीं की गई है, लेकिन यह संबंधित हो सकता है: shipilev.net/blog/2015/black-magic-method-dispatch (शायद एलेक्सी शिपिलव हमें यहां बता सकते हैं)
मार्कोल

जवाबों:


72

JRE 1.8.0_45 के उपयोग से मुझे समान परिणाम मिलते हैं।

जाँच पड़ताल:

  1. साथ जावा चल रहा है -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInliningVM विकल्पों के पता चलता है कि दोनों विधियां संकलित और अंतर्निर्मित हैं
  2. तरीकों के लिए उत्पन्न विधानसभा को देखते हुए स्वयं कोई महत्वपूर्ण अंतर नहीं दिखाता है
  3. एक बार जब वे अंतर्निर्मित हो जाते हैं, हालांकि, भीतर उत्पन्न विधानसभा mainबहुत अलग होती है, उदाहरण के तरीके को अधिक आक्रामक रूप से अनुकूलित किया जाता है, विशेष रूप से लूप ट्रोलिंग के संदर्भ में

मैंने फिर आपके परीक्षण को फिर से चलाया लेकिन ऊपर दिए गए संदेह की पुष्टि करने के लिए अलग-अलग लूप की सेटिंग के साथ। मैंने आपका कोड इसके साथ चलाया:

  • -XX:LoopUnrollLimit=0 और दोनों विधियां धीरे-धीरे चलती हैं (डिफ़ॉल्ट विकल्पों के साथ स्थिर विधि के समान)।
  • -XX:LoopUnrollLimit=100 और दोनों विधियाँ तेजी से चलती हैं (डिफ़ॉल्ट विकल्पों के साथ उदाहरण विधि के समान)।

निष्कर्ष के रूप में ऐसा लगता है कि, डिफ़ॉल्ट सेटिंग्स के साथ, हॉटस्पॉट 1.8.0_45 का JIT विधि स्थिर होने पर लूप को अनियंत्रित करने में सक्षम नहीं है (हालांकि मुझे यकीन नहीं है कि यह उस तरह से व्यवहार क्यों करता है)। अन्य JVM अलग परिणाम दे सकते हैं।


52 और 71 के बीच, मूल व्यवहार को बहाल किया जाता है (कम से कम मेरी मशीन पर, मेरा जवाब।)। ऐसा लगता है कि स्थैतिक संस्करण 20 इकाइयों से बड़ा था, लेकिन क्यों? यह अजीब है।
माआर्टिनस

3
@maaartinus मुझे यह भी पता नहीं है कि यह संख्या वास्तव में किसका प्रतिनिधित्व करती है - डॉक्टर काफी स्पष्ट है: " सर्वर कंपाइलर इंटरमीडिएट प्रतिनिधित्व नोड के साथ लूप निकायों को इस मान से कम गिना जाता है। सर्वर कंपाइलर द्वारा उपयोग की जाने वाली सीमा इस मान का एक फ़ंक्शन है। वास्तविक मूल्य नहीं । डिफ़ॉल्ट मान उस प्लेटफ़ॉर्म के साथ भिन्न होता है जिस पर JVM चल रहा है। "...
assylias

न तो मुझे पता है, लेकिन मेरा पहला अनुमान यह था कि जो भी इकाइयाँ हैं उनमें स्थैतिक तरीके थोड़े बड़े हो जाते हैं और हम उस स्थान पर पहुँच जाते हैं जहाँ यह मायने रखता है। हालांकि, अंतर बहुत बड़ा है, इसलिए मेरा वर्तमान अनुमान यह है कि स्थिर संस्करण को कुछ अनुकूलन मिलते हैं जो इसे काफी बड़ा बनाते हैं। मैं उत्पन्न asm को नहीं देखा है।
माॅर्टिनस

33

बस एक अप्रमाणित अनुमान एक एसिलियास के उत्तर पर आधारित है।

JVM लूप अनरोलिंग के लिए थ्रेशोल्ड का उपयोग करता है, जो कि 70 जैसा कुछ है। जो भी कारण से, स्थिर कॉल थोड़ा बड़ा है और अनियंत्रित नहीं होता है।

अद्यतन परिणाम

  • उसके साथ LoopUnrollLimit 52 नीचे में, दोनों संस्करणों धीमी गति से कर रहे हैं।
  • 52 और 71 के बीच, केवल स्थिर संस्करण धीमा है।
  • 71 से ऊपर, दोनों संस्करण तेज हैं।

यह अजीब है क्योंकि मेरा अनुमान था कि स्टेटिक कॉल आंतरिक प्रतिनिधित्व में थोड़ा बड़ा है और ओपी ने एक अजीब मामला मारा। लेकिन अंतर लगभग 20 का लगता है, जिसका कोई मतलब नहीं है।

 

-XX:LoopUnrollLimit=51
5400 ms NON_STATIC
5310 ms STATIC
-XX:LoopUnrollLimit=52
1456 ms NON_STATIC
5305 ms STATIC
-XX:LoopUnrollLimit=71
1459 ms NON_STATIC
5309 ms STATIC
-XX:LoopUnrollLimit=72
1457 ms NON_STATIC
1488 ms STATIC

प्रयोग करने के इच्छुक लोगों के लिए, मेरा संस्करण उपयोगी हो सकता है।


क्या '1456 एमएस' समय है? यदि हां, तो आप कहते हैं कि स्थैतिक धीमा क्यों है?
टोनी

@ मैं उलझन में था NON_STATICऔर STATIC, लेकिन मेरा निष्कर्ष सही था। अब निश्चित है, धन्यवाद।
माआर्टिनस

0

जब इसे डीबग मोड में निष्पादित किया जाता है, तो संख्या उदाहरण और स्थिर मामलों के लिए समान होती है। इसका मतलब यह है कि जेआईटी स्टेटिक कोड को स्टैटिक केस में उसी तरह संकलित करने में हिचकिचाता है, जिस तरह से यह इंस्टीट्यूशन केस में करता है।

ऐसा क्यों करता है? यह कहना कठिन है; अगर यह एक बड़ा अनुप्रयोग होता तो शायद यह सही काम करता ...


"यह ऐसा क्यों करता है? कहना मुश्किल है, शायद यह सही काम करेगा अगर यह एक बड़ा ऐप था।" या आपको बस एक अजीब प्रदर्शन समस्या होगी जो वास्तव में डीबग करने के लिए बहुत बड़ी है। (और यह कहना मुश्किल नहीं है। आप असेंबली की तरह जेवीएम के भाषणों को विधानसभा में देख सकते हैं।)
tmyklebu

@tmyklebu या हमारे पास एक अजीब प्रदर्शन समस्या है जो पूरी तरह से डिबग करने के लिए अनावश्यक और महंगी है और आसान वर्कअराउंड हैं। अंत में, हम यहां जेआईटी के बारे में बात कर रहे हैं, इसके लेखक नहीं जानते कि यह सभी स्थितियों में बिल्कुल कैसे व्यवहार करता है। :) अन्य उत्तरों को देखें, वे इस मुद्दे को समझाने के लिए बहुत अच्छे और बहुत करीबी हैं, लेकिन अभी तक कोई नहीं जानता कि वास्तव में ऐसा क्यों हो रहा है।
ड्रेगन बोजानोविक

@DraganBozanovic: जब यह वास्तविक कोड में वास्तविक मुद्दों का कारण बनता है तो "अनावश्यक रूप से पूरी तरह से डीबग करना" बंद हो जाता है।
tmyklebu

0

मैंने परीक्षण को थोड़ा मोड़ दिया और मुझे निम्नलिखित परिणाम मिले:

आउटपुट:

Dynamic Test:
465585120
232792560
232792560
51350 ms
Static Test:
465585120
232792560
232792560
52062 ms

ध्यान दें

जब मैं उन्हें अलग से परीक्षण कर रहा था तो मुझे गतिशील के लिए ~ 52 सेकंड और स्थैतिक के लिए ~ 200 सेकंड मिला।

यह कार्यक्रम है:

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {  // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    static int twentyDivCount2(int a) {
         int count = 0;
         for (int i = 1; i<21; i++) {

             if (a % i == 0) {
                 count++;
             }
         }
         return count;
    }

    public static void main(String[] args) {
        System.out.println("Dynamic Test: " );
        dynamicTest();
        System.out.println("Static Test: " );
        staticTest();
    }

    private static void staticTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        for (int i = start; i > 0; i--) {

            int temp = twentyDivCount2(i);

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }

    private static void dynamicTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

मैंने परीक्षण का क्रम भी बदल दिया है:

public static void main(String[] args) {
    System.out.println("Static Test: " );
    staticTest();
    System.out.println("Dynamic Test: " );
    dynamicTest();
}

और मुझे यह मिल गया:

Static Test:
465585120
232792560
232792560
188945 ms
Dynamic Test:
465585120
232792560
232792560
50106 ms

जैसा कि आप देखते हैं, यदि गतिशील को स्थिर से पहले कहा जाता है, तो स्थिर के लिए गति नाटकीय रूप से कम हो जाती है।

इस बेंचमार्क पर आधारित:

मैं परिकल्पना करता हूं कि यह सब जेवीएम अनुकूलन पर निर्भर करता है। इस प्रकार मैं आपको केवल स्थैतिक और गतिशील तरीकों के उपयोग के लिए अंगूठे के नियम के साथ जाने की सलाह देता हूं।

अंगूठे का नियम:

जावा: जब स्थिर विधियों का उपयोग करना है


"आप स्थैतिक और गतिशील तरीकों के उपयोग के लिए अंगूठे के नियम के साथ जा सकते हैं।" अंगूठे का यह नियम क्या है? और आप किस / से उद्धृत कर रहे हैं?
पश्चिम में

@ वेस्टन माफ करना मैं वह लिंक नहीं जोड़ पाया जो मैं सोच रहा था :)। thx
nafas

0

प्रयास करें:

public class ProblemFive {
    public static ProblemFive PROBLEM_FIVE = new ProblemFive();

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();
        int start = 500000000;
        int result = start;


        for (int i = start; i > 0; i--) {
            int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way
            // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
                System.out.println((System.currentTimeMillis() - startT) + " ms");
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();
        System.out.println((end - startT) + " ms");
    }

    int twentyDivCount(int a) {  // change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i < 21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }
}

20273 एमएस से 23000+ एमएस, प्रत्येक रन के लिए अलग
स्टैब्ज़
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.