क्यों है (अगर वेरिएबल% वेरिएबल 2 == 0) अक्षम है?


179

मैं जावा में नया हूं, और कल रात कुछ कोड चला रहा था, और इसने मुझे परेशान कर दिया। मैं लूप के लिए हर एक्स आउटपुट को प्रदर्शित करने के लिए एक सरल प्रोग्राम बना रहा था, और मैंने प्रदर्शन में कमी देखी, जब मैंने variable % variableबनाम variable % 5000या व्हाट्सएप के रूप में मापांक का उपयोग किया । क्या कोई मुझे समझा सकता है कि यह क्यों है और यह क्या कारण है? तो मैं बेहतर हो सकता है ...

यहाँ "कुशल" कोड है (क्षमा करें अगर मुझे थोड़ा सा वाक्यविन्यास गलत मिला तो मैं अभी कोड वाले कंप्यूटर पर नहीं हूँ)

long startNum = 0;
long stopNum = 1000000000L;

for (long i = startNum; i <= stopNum; i++){
    if (i % 50000 == 0) {
        System.out.println(i);
    }
}

यहाँ "अक्षम कोड" है

long startNum = 0;
long stopNum = 1000000000L;
long progressCheck = 50000;

for (long i = startNum; i <= stopNum; i++){
    if (i % progressCheck == 0) {
        System.out.println(i);
    }
}

ध्यान रखें कि मेरे पास मतभेदों को मापने के लिए एक दिनांक चर था, और एक बार जब यह काफी लंबा हो गया, तो पहले वाले ने 50ms लिए जबकि दूसरे ने 12 सेकंड या ऐसा ही कुछ लिया। यदि आपका पीसी खदान से अधिक कुशल है या नहीं तो आपको बढ़ाना stopNumया घटाना पड़ सकता है progressCheck

मैंने वेब पर इस प्रश्न की तलाश की, लेकिन मुझे इसका उत्तर नहीं मिला, शायद मैं इसे सही नहीं कह रहा हूँ।

संपादित करें: मुझे उम्मीद नहीं थी कि मेरा प्रश्न इतना लोकप्रिय होगा, मैं सभी उत्तरों की सराहना करता हूं। मैंने प्रत्येक आधे समय में एक बेंचमार्क का प्रदर्शन किया, और अक्षम कोड काफी लंबा समय लगा, 1/4 एक सेकंड बनाम 10 सेकंड दे या ले। दी है कि वे प्रिंटलाइन का उपयोग कर रहे हैं, लेकिन वे दोनों एक ही राशि कर रहे हैं, इसलिए मैं कल्पना नहीं करूंगा कि यह बहुत तिरछा होगा, खासकर जब से विसंगति दोहराने योग्य है। जवाब के लिए, चूंकि मैं जावा में नया हूं, इसलिए मैं वोटों को अभी तय करने दूंगा कि कौन सा जवाब सबसे अच्छा है। मैं बुधवार तक इसे चुनने की कोशिश करूंगा।

EDIT2: मैं आज रात एक और परीक्षण करने जा रहा हूं, जहां मापांक के बजाय, यह केवल एक चर बढ़ाता है, और जब यह प्रगति पर पहुंचता है, तो यह एक प्रदर्शन करेगा, और फिर उस चर को 0. एक 3 विकल्प के लिए रीसेट कर देगा।

EDIT3.5:

मैंने इस कोड का उपयोग किया है, और नीचे मैं अपने परिणाम दिखाऊंगा .. अद्भुत मदद के लिए धन्यवाद! मैंने भी लंबे समय से 0 के छोटे मूल्य की तुलना करने की कोशिश की, इसलिए मेरे सभी नए चेक कभी-कभी "65536" बार दोहराते हैं।

public class Main {


    public static void main(String[] args) {

        long startNum = 0;
        long stopNum = 1000000000L;
        long progressCheck = 65536;
        final long finalProgressCheck = 50000;
        long date;

        // using a fixed value
        date = System.currentTimeMillis();
        for (long i = startNum; i <= stopNum; i++) {
            if (i % 65536 == 0) {
                System.out.println(i);
            }
        }
        long final1 = System.currentTimeMillis() - date;
        date = System.currentTimeMillis();
        //using a variable
        for (long i = startNum; i <= stopNum; i++) {
            if (i % progressCheck == 0) {
                System.out.println(i);
            }
        }
        long final2 = System.currentTimeMillis() - date;
        date = System.currentTimeMillis();

        // using a final declared variable
        for (long i = startNum; i <= stopNum; i++) {
            if (i % finalProgressCheck == 0) {
                System.out.println(i);
            }
        }
        long final3 = System.currentTimeMillis() - date;
        date = System.currentTimeMillis();
        // using increments to determine progressCheck
        int increment = 0;
        for (long i = startNum; i <= stopNum; i++) {
            if (increment == 65536) {
                System.out.println(i);
                increment = 0;
            }
            increment++;

        }

        //using a short conversion
        long final4 = System.currentTimeMillis() - date;
        date = System.currentTimeMillis();
        for (long i = startNum; i <= stopNum; i++) {
            if ((short)i == 0) {
                System.out.println(i);
            }
        }
        long final5 = System.currentTimeMillis() - date;

                System.out.println(
                "\nfixed = " + final1 + " ms " + "\nvariable = " + final2 + " ms " + "\nfinal variable = " + final3 + " ms " + "\nincrement = " + final4 + " ms" + "\nShort Conversion = " + final5 + " ms");
    }
}

परिणाम:

  • निश्चित = 874 एमएस (सामान्य रूप से लगभग 1000 सेमी, लेकिन 2 की शक्ति होने के कारण तेजी से)
  • चर = 8590 एमएस
  • अंतिम चर = 1944 एमएस (50000 का उपयोग करते समय (~ 1000 मी।)
  • वेतन वृद्धि = 1904 एमएस
  • लघु रूपांतरण = 679 एमएस

आश्चर्य की बात नहीं है, विभाजन की कमी के कारण, लघु रूपांतरण "तेज" तरीके से 23% तेज था। यह नोट करना दिलचस्प है। यदि आपको प्रत्येक 256 बार (या वहां के बारे में) कुछ दिखाने या तुलना करने की आवश्यकता है, तो आप ऐसा कर सकते हैं, और उपयोग कर सकते हैं

if ((byte)integer == 0) {'Perform progress check code here'}

एक अंतिम अंतर नोट, 65536 के साथ "अंतिम घोषित चर" पर मापांक का उपयोग करते हुए (एक सुंदर संख्या नहीं) निश्चित मूल्य की तुलना में धीमी (धीमी) थी। जहां पहले यह उसी गति के निकट बेंचमार्किंग कर रहा था।


29
मुझे वास्तव में वही परिणाम मिला। मेरी मशीन पर, पहला लूप लगभग 1,5 सेकंड में और दूसरा लगभग 9 सेकंड में चलता है। यदि मैं चर के finalसामने जोड़ता हूं progressCheck, तो दोनों फिर से उसी गति से चलते हैं। यह मुझे विश्वास दिलाता है कि संकलक या JIT लूप को अनुकूलित करने का प्रबंधन करता है जब यह जानता है कि progressCheckयह स्थिर है।
marstran


24
एक स्थिर द्वारा डिवीजन को गुणक व्युत्क्रम द्वारा आसानी से गुणा में बदला जा सकता है । एक चर द्वारा विभाजन नहीं कर सकते। और एक 32-बिट डिवीजन x86 पर एक 64-बिट डिवीजन से अधिक तेज है
फुलकव

2
@phuclv नोट 32-बिट डिवीजन यहाँ कोई समस्या नहीं है, यह दोनों मामलों में 64-बिट शेष ऑपरेशन है
user85421

4
@RobertCotterman यदि आप चर को अंतिम रूप में घोषित करते हैं, तो कंपाइलर निरंतर (ग्रहण / जावा 11) ((चर के लिए एक और मेमोरी स्लॉट का उपयोग करने के बावजूद) का उपयोग करते हुए एक ही
बायटेकोड बनाता है

जवाबों:


139

आप OSR (ऑन-स्टैक रिप्लेसमेंट) स्टब को माप रहे हैं ।

OSR स्टब संकलित विधि का एक विशेष संस्करण है जो विशेष रूप से व्याख्या की गई विधि से संकलित कोड को स्थानांतरित करने के लिए है, जबकि विधि चल रही है।

ओएसआर स्टब्स नियमित तरीकों के रूप में अनुकूलित नहीं हैं, क्योंकि उन्हें व्याख्या किए गए फ्रेम के साथ संगत एक फ्रेम लेआउट की आवश्यकता होती है। मैंने इसे पहले ही निम्नलिखित उत्तरों में दिखाया: 1 , 2 , 3

इसी तरह की बात यहां भी होती है। जबकि "अक्षम कोड" एक लंबा लूप चला रहा है, विधि लूप के अंदर ऑन-स्टैक रिप्लेसमेंट के लिए विशेष रूप से संकलित है। राज्य को व्याख्यात्मक फ्रेम से OSR- संकलित विधि में स्थानांतरित किया जाता है, और इस राज्य में progressCheckस्थानीय चर शामिल हैं। इस बिंदु पर JIT वैरिएबल को स्थिरांक के साथ प्रतिस्थापित नहीं कर सकता है, और इस प्रकार ताकत में कमी जैसे कुछ अनुकूलन लागू नहीं कर सकता है ।

विशेष रूप से इसका मतलब है कि JIT पूर्णांक विभाजन को गुणा के साथ प्रतिस्थापित नहीं करता है । (देखें कि पूर्णांक विभाजन को लागू करने में GCC एक अजीब संख्या से गुणा का उपयोग क्यों करता है? आगे-समय के संकलक से asm चाल के लिए, जब मान एक संकलन-स्थिर होने के बाद संकलन / निरंतर-प्रसार है, यदि वे अनुकूलन सक्षम हैं। ( %अभिव्यक्ति में एक पूर्णांक शाब्दिक अधिकार भी इसके द्वारा अनुकूलित हो जाता है gcc -O0, यहाँ के समान जहाँ यह एक OSR स्टब में भी JITer द्वारा अनुकूलित है।)

हालाँकि, यदि आप एक ही विधि को कई बार चलाते हैं, तो दूसरा और बाद का रन नियमित (गैर-OSR) कोड निष्पादित करेगा, जो पूरी तरह से अनुकूलित है। यहाँ सिद्धांत को साबित करने के लिए एक बेंचमार्क है ( जेएमएच का उपयोग करके बेंचमार्क किया गया ):

@State(Scope.Benchmark)
public class Div {

    @Benchmark
    public void divConst(Blackhole blackhole) {
        long startNum = 0;
        long stopNum = 100000000L;

        for (long i = startNum; i <= stopNum; i++) {
            if (i % 50000 == 0) {
                blackhole.consume(i);
            }
        }
    }

    @Benchmark
    public void divVar(Blackhole blackhole) {
        long startNum = 0;
        long stopNum = 100000000L;
        long progressCheck = 50000;

        for (long i = startNum; i <= stopNum; i++) {
            if (i % progressCheck == 0) {
                blackhole.consume(i);
            }
        }
    }
}

और परिणाम:

# Benchmark: bench.Div.divConst

# Run progress: 0,00% complete, ETA 00:00:16
# Fork: 1 of 1
# Warmup Iteration   1: 126,967 ms/op
# Warmup Iteration   2: 105,660 ms/op
# Warmup Iteration   3: 106,205 ms/op
Iteration   1: 105,620 ms/op
Iteration   2: 105,789 ms/op
Iteration   3: 105,915 ms/op
Iteration   4: 105,629 ms/op
Iteration   5: 105,632 ms/op


# Benchmark: bench.Div.divVar

# Run progress: 50,00% complete, ETA 00:00:09
# Fork: 1 of 1
# Warmup Iteration   1: 844,708 ms/op          <-- much slower!
# Warmup Iteration   2: 105,893 ms/op          <-- as fast as divConst
# Warmup Iteration   3: 105,601 ms/op
Iteration   1: 105,570 ms/op
Iteration   2: 105,475 ms/op
Iteration   3: 105,702 ms/op
Iteration   4: 105,535 ms/op
Iteration   5: 105,766 ms/op

divVarवास्तव में बहुत कम धीमी गति से संकलित OSR ठूंठ की वजह से इसका पहला पुनरावृत्ति बहुत धीमा है। लेकिन जैसे ही विधि शुरुआत से फिर से शुरू होती है, नए अप्रतिबंधित संस्करण को निष्पादित किया जाता है जो सभी उपलब्ध संकलक अनुकूलन का लाभ उठाता है।


5
मैं इस पर मतदान करने में संकोच करता हूं। एक तरफ, यह कहने का एक विस्तृत तरीका लगता है "आपने अपने बेंचमार्क को गड़बड़ कर दिया, जेआईटी के बारे में कुछ पढ़ा"। दूसरी ओर, मुझे आश्चर्य है कि आपको ऐसा क्यों लगता है कि OSR यहाँ मुख्य प्रासंगिक बिंदु था। मेरा मतलब है, एक (सूक्ष्म) बेंचमार्क जिसमें शामिल System.out.printlnकरना लगभग आवश्यक रूप से कचरा परिणाम पैदा करेगा , और यह तथ्य कि दोनों संस्करण समान रूप से तेज़ हैं , विशेष रूप से
ओएसआर के

2
(मैं उत्सुक हूं और इसे समझना पसंद करता हूं। मुझे आशा है कि टिप्पणियां परेशान नहीं कर रही हैं, बाद में उन्हें हटा सकती हैं, लेकिन 1:) लिंक थोड़ा संदिग्ध है - खाली लूप भी पूरी तरह से अनुकूलित किया जा सकता है। दूसरा उस एक के समान है। लेकिन फिर से, यह स्पष्ट नहीं है कि आप विशेष रूप से ओएसआर के लिए अंतर क्यों रखते हैं । मैं सिर्फ इतना कहना चाहता हूं: कुछ बिंदु पर, विधि JITed है और तेज हो जाती है। मेरी समझ में, OSR केवल अंतिम, अनुकूलित कोड के उपयोग का कारण बनता है (मोटे तौर पर) ~ "अगले अनुकूलन पास के लिए स्थगित"। (जारी ...)
मार्को 13

1
(जारी :) जब तक आप विशेष रूप से हॉटस्पॉट लॉग का विश्लेषण नहीं कर रहे हैं, आप यह नहीं कह सकते हैं कि अंतर JITed और संयुक्त राष्ट्र JITed की तुलना के कारण होता है या JITed और OSR- स्टब-कोड की तुलना करके। और आप निश्चित रूप से यह नहीं कह सकते कि निश्चित रूप से जब प्रश्न में वास्तविक कोड या पूरा JMH बेंचमार्क नहीं है। इसलिए यह तर्क देते हुए कि अंतर ओएसआर ध्वनियों के कारण होता है, मेरे लिए, अनुचित रूप से विशिष्ट (और "अनुचित") यह कहने की तुलना में कि यह सामान्य रूप से जेआईटी के कारण होता है। (कोई अपराध नहीं - मैं बस सोच रहा हूँ ...)
Marco13

4
@ मार्को 13 एक सरल अनुमानक है: जेआईटी की गतिविधि के बिना, प्रत्येक %ऑपरेशन में एक ही वजन होगा, क्योंकि एक अनुकूलित निष्पादन केवल संभव है, ठीक है, अगर एक अनुकूलक ने वास्तविक काम किया। इसलिए तथ्य यह है कि एक लूप वेरिएंट दूसरे की तुलना में काफी तेज है, एक ऑप्टिमाइज़र की उपस्थिति को साबित करता है और आगे यह साबित करता है कि यह लूप्स में से एक को दूसरे के समान डिग्री (एक ही विधि के भीतर) के अनुकूलन में विफल रहा। जैसा कि यह उत्तर दोनों छोरों को एक ही डिग्री पर अनुकूलित करने की क्षमता को साबित करता है, कुछ ऐसा होना चाहिए जो अनुकूलन में बाधा बने। और वह सभी मामलों में 99.9% में OSR है
Holger

4
@ मार्को 13 यह हॉटस्पॉट रनटाइम के ज्ञान और पहले के समान मुद्दों का विश्लेषण करने के अनुभव के आधार पर एक "शिक्षित अनुमान" था। इस तरह के लंबे लूप को शायद ही OSR के अलावा किसी अन्य तरीके से संकलित किया जा सकता है, विशेष रूप से साधारण हाथ से बने बेंचमार्क में। अब, जब ओपी ने पूरा कोड पोस्ट किया है, तो मैं केवल एक बार फिर से कोड चलाकर तर्क की पुष्टि कर सकता हूं -XX:+PrintCompilation -XX:+TraceNMethodInstalls
अपंगिन

42

@Phuclv टिप्पणी के अनुवर्ती में , मैंने JIT 1 द्वारा उत्पन्न कोड की जाँच की , परिणाम इस प्रकार हैं:

के लिए variable % 5000(निरंतर द्वारा विभाजन):

mov     rax,29f16b11c6d1e109h
imul    rbx
mov     r10,rbx
sar     r10,3fh
sar     rdx,0dh
sub     rdx,r10
imul    r10,rdx,0c350h    ; <-- imul
mov     r11,rbx
sub     r11,r10
test    r11,r11
jne     1d707ad14a0h

के लिए variable % variable:

mov     rax,r14
mov     rdx,8000000000000000h
cmp     rax,rdx
jne     22ccce218edh
xor     edx,edx
cmp     rbx,0ffffffffffffffffh
je      22ccce218f2h
cqo
idiv    rax,rbx           ; <-- idiv
test    rdx,rdx
jne     22ccce218c0h

क्योंकि विभाजन हमेशा गुणा से अधिक समय लेता है, अंतिम कोड स्निपेट कम प्रदर्शन करने वाला होता है।

जावा संस्करण:

java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)

1 - वीएम विकल्प का उपयोग: -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,src/java/Main.main


14
"धीमी" पर परिमाण का एक आदेश देने के लिए, x86_64 के लिए: imul3 चक्र है, idiv30 और 90 चक्रों के बीच है। तो पूर्णांक विभाजन पूर्णांक गुणन की तुलना में 10x और 30x धीमा होता है।
Matthieu M.

2
क्या आप बता सकते हैं कि रुचि रखने वाले पाठकों के लिए इसका क्या अर्थ है, लेकिन कोडांतरक नहीं बोलते हैं?
निको हसे

7
@NicoHaase दो टिप्पणी लाइनों केवल महत्वपूर्ण हैं। पहले खंड में, कोड एक पूर्णांक गुणा कर रहा है, जबकि दूसरे खंड में, कोड एक पूर्णांक विभाजन कर रहा है। यदि आप गुणा और विभाजन को हाथ से करने के बारे में सोचते हैं, जब आप गुणा करते हैं तो आप आमतौर पर छोटे गुणा और फिर जोड़ का एक बड़ा समूह बनाते हैं, लेकिन विभाजन एक छोटा विभाजन, एक छोटा गुणा, घटाव और दोहराव होता है। विभाजन धीमा है क्योंकि आप अनिवार्य रूप से गुणा का एक गुच्छा बना रहे हैं।
MBraedley

4
@MBraedley आपके इनपुट के लिए धन्यवाद, लेकिन इस तरह के स्पष्टीकरण को उत्तर में ही जोड़ा जाना चाहिए और टिप्पणी अनुभाग में छिपाया नहीं जाना चाहिए
निको हसे

6
@MBraedley: इस बिंदु से अधिक, एक आधुनिक सीपीयू में गुणा तेज है क्योंकि आंशिक उत्पाद स्वतंत्र हैं और इस प्रकार अलग से गणना की जा सकती है, जबकि एक विभाजन का प्रत्येक चरण पूर्ववर्ती चरणों पर निर्भर है।
सुपरकैट

26

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

long progressCheck = 50000;

long counter = progressCheck;

for (long i = startNum; i <= stopNum; i++){
    if (--counter == 0) {
        System.out.println(i);
        counter = progressCheck;
    }
}

(एक मामूली ऑप्टिमाइज़ेशन प्रयास के रूप में, हम यहाँ एक पूर्व-डिक्रीमेंट डाउन-काउंटर का उपयोग करते हैं क्योंकि 0अंकगणितीय ऑपरेशन के तुरंत बाद की तुलना में कई आर्किटेक्चर पर ठीक 0 निर्देश / सीपीयू चक्र की लागत होती है क्योंकि एएलयू के झंडे पहले से ही उपयुक्त संचालन द्वारा उचित रूप से सेट हैं। एक सभ्य अनुकूलन। संकलक, हालांकि, उस अनुकूलन को स्वचालित रूप से करते हैं भले ही आप लिखते हों if (counter++ == 50000) { ... counter = 0; }।)

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

एक और 'ट्रिक' पावर टू ऑफ टू वैल्यू / लिमिट्स का उपयोग करना है, जैसे progressCheck = 1024;। मोडुलस की शक्ति को दो बिट्स के माध्यम से जल्दी से गणना की जा सकती है and, अर्थात if ( (i & (1024-1)) == 0 ) {...}। यह बहुत तेज होना चाहिए, और कुछ आर्किटेक्चर पर counterऊपर स्पष्ट रूप से बेहतर प्रदर्शन कर सकते हैं।


3
एक स्मार्ट कंपाइलर यहां छोरों को उलट देगा। या आप स्रोत में ऐसा कर सकते हैं। if()शरीर एक बाहरी लूप शरीर हो जाता है, और बाहर सामान if()एक आंतरिक पाश शरीर हो जाता है कि के लिए रन min(progressCheck, stopNum-i)पुनरावृत्तियों। इसलिए शुरुआत में, और हर बार counter0 तक पहुंचने पर, आप लूप के long next_stop = i + min(progressCheck, stopNum-i);लिए सेट अप करते हैं for(; i< next_stop; i++) {}। इस मामले में कि आंतरिक लूप खाली है और उम्मीद है कि पूरी तरह से दूर हो जाए, आप स्रोत में ऐसा कर सकते हैं और इसे आसान बना सकते हैं और अपने पाश को i + = 50k तक कम कर सकते हैं।
पीटर कॉर्डेस

2
लेकिन हाँ, सामान्य तौर पर एक डाउन-काउंटर, फ़िज़बज़ / प्रोग्रेसचेक प्रकार के सामान के लिए एक अच्छी कुशल तकनीक है।
पीटर कॉर्डेस

मैंने अपने प्रश्न में जोड़ा, और एक वेतन वृद्धि की, --counterमेरे वेतन वृद्धि संस्करण की तरह ही तेज़ है, लेकिन कम कोड। इसके अलावा यह 1 से कम होना चाहिए, मुझे उत्सुक होना चाहिए counter--कि क्या आप चाहते हैं कि यह सटीक संख्या हो। , यह नहीं कि यह बहुत अंतर है
रॉबर्ट कॉटरमैन

@PeterCordes एक स्मार्ट कंपाइलर केवल संख्याओं को प्रिंट करेगा, कोई भी लूप नहीं। (मुझे लगता है कि कुछ केवल कुछ अधिक तुच्छ मानदंड 10 साल पहले शायद उस तरह से विफल होने लगे।)
पीटर - मोनिका

2
@RobertCotterman हां, --counterएक बंद है। counter--आपको progressCheckपुन: पुनरावृत्तियों की संख्या देगा (या आप progressCheck = 50001;निश्चित रूप से सेट कर सकते हैं )।
जिमीबी

4

उपरोक्त कोड के प्रदर्शन को देखकर मैं भी आश्चर्यचकित हूं। यह सभी घोषित चर के अनुसार प्रोग्राम को निष्पादित करने के लिए संकलक द्वारा लिए गए समय के बारे में है। दूसरे (अकुशल) उदाहरण में:

for (long i = startNum; i <= stopNum; i++) {
    if (i % progressCheck == 0) {
        System.out.println(i)
    }
}

आप दो चर के बीच मापांक ऑपरेशन कर रहे हैं। यहां, संकलक को प्रत्येक पुनरावृत्ति के बाद हर बार इन चरों के लिए स्थित विशिष्ट मेमोरी ब्लॉक में जाने के लिए stopNumऔर उसके मूल्य की जांच progressCheckकरनी होती है क्योंकि यह एक चर है और इसका मान बदल सकता है।

यही कारण है कि प्रत्येक पुनरावृत्ति संकलक के बाद चर का नवीनतम मान जांचने के लिए मेमोरी लोकेशन पर जाता है। इसलिए संकलन के समय संकलक कुशल बाइट कोड बनाने में सक्षम नहीं था।

पहले कोड उदाहरण में, आप एक चर और एक निरंतर संख्यात्मक मान के बीच मापांक ऑपरेटर का प्रदर्शन कर रहे हैं जो निष्पादन और संकलक के भीतर बदलने नहीं जा रहा है, स्मृति स्थान से उस संख्यात्मक मान के मूल्य की जांच करने की कोई आवश्यकता नहीं है। यही कारण है कि संकलक कुशल बाइट कोड बनाने में सक्षम था। यदि आप progressCheckएक finalया एक final staticचर के रूप में घोषित करते हैं तो रन-टाइम / कंपाइल-टाइम कंपाइलर के समय जानते हैं कि यह एक अंतिम परिवर्तनशील है और इसका मान बदलने वाला नहीं है तो कंपाइलर कोड के progressCheckसाथ बदल देता है 50000:

for (long i = startNum; i <= stopNum; i++) {
    if (i % 50000== 0) {
        System.out.println(i)
    }
}

अब आप देख सकते हैं कि यह कोड भी पहले (कुशल) कोड उदाहरण की तरह दिखता है। पहले कोड का प्रदर्शन और जैसा कि हमने दोनों कोड के ऊपर उल्लेख किया है, कुशलतापूर्वक काम करेगा। कोड उदाहरण के निष्पादन समय में बहुत अंतर नहीं होगा।


1
एक बड़ा अंतर है, यद्यपि मैं ऑपरेशन को एक ट्रिलियन बार कर रहा था, इसलिए 1 ट्रिलियन से अधिक ऑपरेशनों ने इसे "कुशल" कोड करने के लिए 89% समय बचाया। अगर आप केवल इसे कुछ हज़ार बार कर रहे हैं, तो आप इस तरह के छोटे अंतर पर बात कर रहे थे, यह शायद बड़ी बात नहीं है। मेरा मतलब है कि 1000 से अधिक परिचालनों में यह आपको 7 सेकंड में 1 मिलियन बचाएगा।
रॉबर्ट कॉटरमैन

1
@ बिशाल दुबे "दोनों कोड के निष्पादन समय में बहुत अंतर नहीं होगा।" क्या आपने प्रश्न पढ़ा है?
अनुदान फोस्टर

"यही कारण है कि प्रत्येक पुनरावृत्ति संकलक स्मृति स्थान पर चर के नवीनतम मूल्य की जांच करने के लिए चला गया" - जब तक कि चर को volatile'संकलक' घोषित नहीं किया गया था तब तक रैम से इसके मूल्य को बार-बार नहीं पढ़ा जाएगा ।
जिमीबी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.