प्रतिबिंब के साथ JIT अनुकूलन तोड़ना


9

जब एक उच्च समवर्ती एकल वर्ग के लिए इकाई परीक्षणों के साथ मैं निम्नलिखित अजीब व्यवहार पर ठोकर खाई (JDK 1.8.0_162 पर परीक्षण):

private static class SingletonClass {
    static final SingletonClass INSTANCE = new SingletonClass(0);
    final int value;

    static SingletonClass getInstance() {
        return INSTANCE;
    }

    SingletonClass(int value) {
        this.value = value;
    }
}

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

    System.out.println(SingletonClass.getInstance().value); // 0

    // Change the instance to a new one with value 1
    setSingletonInstance(new SingletonClass(1));
    System.out.println(SingletonClass.getInstance().value); // 1

    // Call getInstance() enough times to trigger JIT optimizations
    for(int i=0;i<100_000;++i){
        SingletonClass.getInstance();
    }

    System.out.println(SingletonClass.getInstance().value); // 1

    setSingletonInstance(new SingletonClass(2));
    System.out.println(SingletonClass.INSTANCE.value); // 2
    System.out.println(SingletonClass.getInstance().value); // 1 (2 expected)
}

private static void setSingletonInstance(SingletonClass newInstance) throws NoSuchFieldException, IllegalAccessException {
    // Get the INSTANCE field and make it accessible
    Field field = SingletonClass.class.getDeclaredField("INSTANCE");
    field.setAccessible(true);

    // Remove the final modifier
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    // Set new value
    field.set(null, newInstance);
}

मुख्य () विधि की अंतिम 2 पंक्तियाँ INSTANCE के मूल्य पर असहमत हैं - मेरा अनुमान है कि JIT ने विधि से पूरी तरह छुटकारा पा लिया क्योंकि क्षेत्र स्थिर अंतिम है। अंतिम कीवर्ड को हटाने से कोड आउटपुट सही मान बन जाता है।

सिंगल के लिए अपनी सहानुभूति (या इसके अभाव) को छोड़कर एक मिनट के लिए भूल जाते हैं कि इस तरह प्रतिबिंब का उपयोग करना मुसीबत के लिए पूछ रहा है - क्या मेरी धारणा सही है कि जेआईटी अनुकूलन को दोष देना है? यदि हां - तो क्या वे केवल स्थैतिक अंतिम क्षेत्रों तक सीमित हैं?


1
एक सिंगलटन एक ऐसा वर्ग है जिसके लिए केवल एक उदाहरण मौजूद हो सकता है। इसलिए, आपके पास एक एकल नहीं है, आपके पास एक static finalक्षेत्र के साथ एक वर्ग है । इसके अलावा, इससे कोई फर्क नहीं पड़ता कि यह प्रतिबिंब हैक JIT या संगामिति के कारण टूटता है या नहीं।
होल्गर

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

1
ठीक है, आपने अपने प्रश्न में "उच्च-समवर्ती एकल वर्ग" कहा था और मैं कहता हूं कि " इससे कोई फर्क नहीं पड़ता " क्या इसे तोड़ता है। इसलिए यदि आपका विशेष उदाहरण कोड JIT के कारण टूट जाता है और आपको उसके लिए एक काम-आस-पास मिल जाता है और फिर, JIT के कारण टूटने से वास्तविक कोड बदल जाता है, तो संगामिति के कारण टूट जाता है, आपने क्या हासिल किया है?
होल्गर

@ हैल्गर ठीक है, शब्दांकन थोड़ा बहुत मजबूत था, इस बारे में खेद है। मेरे कहने का मतलब यह था - अगर हमें समझ में नहीं आता है कि क्यों कुछ बहुत बुरी तरह से गलत हो जाता है तो हम भविष्य में एक ही चीज से काटे जाने का शिकार होते हैं, इसलिए मैं यह मानने के बजाय इसका कारण जानता हूं कि "यह बस होता है"। वैसे भी, जवाब देने के लिए अपना समय निकालने के लिए धन्यवाद!
केलम

जवाबों:


7

आपके प्रश्न का शाब्दिक अर्थ, " ... मेरी धारणा सही है कि जेआईटी अनुकूलन में दोष हैं? ", इसका उत्तर हां है, यह बहुत संभावना है कि जेआईटी अनुकूलन इस विशिष्ट उदाहरण में इस व्यवहार के लिए जिम्मेदार हैं।

लेकिन चूंकि बदलते static finalक्षेत्र पूरी तरह से विनिर्देशन से दूर हैं, इसलिए अन्य चीजें हैं जो इसे इसी तरह तोड़ सकती हैं। उदाहरण के लिए, JMM के पास ऐसे परिवर्तनों की मेमोरी दृश्यता के लिए कोई परिभाषा नहीं है, इसलिए, यह पूरी तरह से अनिर्दिष्ट है कि क्या या जब अन्य थ्रेड्स ऐसे परिवर्तनों को नोटिस करते हैं। उन्हें इसे लगातार नोटिस करने की भी आवश्यकता नहीं है, अर्थात वे नए मूल्य का उपयोग कर सकते हैं, इसके बाद फिर से पुराने मूल्य का उपयोग करके, यहां तक ​​कि सिंक्रनाइज़ेशन प्राइमेटिव्स की उपस्थिति में भी।

हालांकि, झामुमो और आशावादी को वैसे भी अलग करना कठिन है।

आपका प्रश्न “ … क्या वे केवल स्थैतिक अंतिम क्षेत्रों तक सीमित हैं? "उत्तर देना बहुत कठिन है, क्योंकि अनुकूलन, निश्चित रूप से, static finalखेतों तक सीमित नहीं हैं, लेकिन गैर-स्थिर finalक्षेत्रों जैसे, का व्यवहार समान नहीं है और सिद्धांत और व्यवहार के बीच भी अंतर है।

गैर-स्थिर finalक्षेत्रों के लिए, कुछ परिस्थितियों में प्रतिबिंब के माध्यम से संशोधनों की अनुमति है। यह इस तथ्य से संकेत मिलता है कि आंतरिक क्षेत्र को बदलने के setAccessible(true)लिए Fieldउदाहरण में हैक किए बिना, इस तरह के संशोधन को संभव बनाने के लिए पर्याप्त है modifiers

विनिर्देश कहते हैं:

17.5.3। बाद में finalफ़ील्ड्स का संशोधन

कुछ मामलों में, जैसे कि deserialization, सिस्टम को finalनिर्माण के बाद किसी ऑब्जेक्ट के फ़ील्ड को बदलने की आवश्यकता होगी । finalफ़ील्ड्स को प्रतिबिंब और अन्य कार्यान्वयन-निर्भर साधनों के माध्यम से बदला जा सकता है। एकमात्र पैटर्न जिसमें यह उचित शब्दार्थ है, वह है जिसमें किसी ऑब्जेक्ट का निर्माण किया जाता है और फिर finalऑब्जेक्ट के फ़ील्ड अपडेट किए जाते हैं। ऑब्जेक्ट को अन्य थ्रेड्स के लिए दृश्यमान नहीं किया जाना चाहिए, और न ही finalफ़ील्ड्स को पढ़ना चाहिए , जब तक finalकि ऑब्जेक्ट के फ़ील्ड्स के सभी अपडेट पूर्ण न हों। किसी finalफ़ील्ड के फ़्रीजर्स , कंस्ट्रक्टर के अंत में दोनों होते हैं जिसमें finalफ़ील्ड सेट होता है, और finalप्रतिबिंब या अन्य विशेष तंत्र के माध्यम से फ़ील्ड के प्रत्येक संशोधन के तुरंत बाद ।

...

एक और समस्या यह है कि विनिर्देश finalक्षेत्रों के आक्रामक अनुकूलन की अनुमति देता है । एक थ्रेड के भीतर, किसी finalफ़ील्ड के उन संशोधनों के साथ फ़ील्ड के रीडर्स को फिर से लिखना अनुमत है finalजो कंस्ट्रक्टर में जगह नहीं लेते हैं।

उदाहरण 17.5.3-1। finalखेतों का आक्रामक अनुकूलन
class A {
    final int x;
    A() { 
        x = 1; 
    } 

    int f() { 
        return d(this,this); 
    } 

    int d(A a1, A a2) { 
        int i = a1.x; 
        g(a1); 
        int j = a2.x; 
        return j - i; 
    }

    static void g(A a) { 
        // uses reflection to change a.x to 2 
    } 
}

में dविधि, संकलक की पढ़ता को पुन: व्यवस्थित करने के लिए अनुमति दी है xऔर करने के लिए कॉल gस्वतंत्र रूप से। इस प्रकार, new A().f()लौट सकता है -1, 0या 1

व्यवहार में, सही स्थानों का निर्धारण करना जहां ऊपर वर्णित कानूनी परिदृश्यों को तोड़ने के बिना आक्रामक अनुकूलन संभव है, एक खुला मुद्दा है , इसलिए जब तक -XX:+TrustFinalNonStaticFieldsनिर्दिष्ट नहीं किया गया है, हॉटस्पॉट जेवीएम गैर-स्थिर finalक्षेत्रों को उसी तरह से अनुकूलित नहीं करेगा जैसे static finalफ़ील्ड।

बेशक, जब आप क्षेत्र को घोषित नहीं करते हैं final, तो जेआईटी यह नहीं मान सकता है कि यह कभी नहीं बदलेगा, हालांकि, थ्रेड सिंक्रोनाइज़ेशन प्राइमेटिव्स की अनुपस्थिति में, यह कोड पथ में होने वाले वास्तविक संशोधनों पर विचार कर सकता है जो इसे अनुकूलित करता है (सहित) चिंतनशील वाले)। तो यह अभी भी आक्रामक रूप से पहुंच का अनुकूलन कर सकता है, लेकिन केवल -अगर पढ़ता है और लिखता है तब भी निष्पादित थ्रेड के भीतर कार्यक्रम क्रम में होता है। इसलिए आप उचित अनुकूलन के निर्माण के बिना एक अलग थ्रेड से इसे देखते हुए केवल अनुकूलन पर ध्यान देंगे।


ऐसा लगता है कि बहुत से लोग इस शोषण का प्रयास करते हैं final, लेकिन, हालांकि कुछ बेहतर प्रदर्शन करने वाले साबित हुए हैं, कुछ को बचाने के लिए nsअन्य कोड को तोड़ने के लायक नहीं है। यही कारण है कि शेनानोआह इसके कुछ झंडों का समर्थन कर रहा है उदाहरण के लिए
यूजीन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.