पहले या पाश में घोषित चर के बीच अंतर?


312

मैं हमेशा सोचता था कि क्या, सामान्य तौर पर, लूप से पहले थ्रो-दूर वैरिएबल की घोषणा करना, जैसा कि लूप के अंदर बार-बार विरोध किया जाता है, किसी भी (प्रदर्शन) अंतर बनाता है? जावा में एक (काफी व्यर्थ) उदाहरण:

क) लूप से पहले घोषणा:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

ख) घोषणा (बार-बार) लूप के अंदर:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

कौन सा बेहतर है, एक या बी ?

मुझे संदेह है कि बार-बार परिवर्तनीय घोषणा (उदाहरण बी ) सिद्धांत में अधिक ओवरहेड बनाता है , लेकिन यह संकलक काफी स्मार्ट होते हैं ताकि कोई फर्क न पड़े। उदाहरण b में अधिक कॉम्पैक्ट होने और चर का दायरा सीमित करने का लाभ है जहां इसका उपयोग किया जाता है। फिर भी, मैं उदाहरण के अनुसार कोड के लिए करते हैं एक

संपादित करें: मुझे जावा मामले में विशेष रूप से दिलचस्पी है।


Android प्लेटफ़ॉर्म के लिए जावा कोड लिखते समय यह बात मायने रखती है। Google सुझाव देता है कि टाइम-क्रिटिकल कोड के लिए फॉर-लूप के बाहर इंक्रीमेंटिंग वैरिएबल घोषित करने के लिए, जैसे कि फॉर-लूप के अंदर, यह उस वातावरण में हर बार इसे दोबारा घोषित करता है। महंगे एल्गोरिदम के लिए प्रदर्शन अंतर बहुत ध्यान देने योग्य है।
एरोनकारसन

1
@AaronCarson क्या आप Google द्वारा इस सुझाव का लिंक प्रदान कर सकते हैं
विटाली ज़िनचेंको

जवाबों:


256

कौन सा बेहतर है, एक या बी ?

प्रदर्शन के दृष्टिकोण से, आपको इसे मापना होगा। (और मेरी राय में, यदि आप एक अंतर को माप सकते हैं, तो संकलक बहुत अच्छा नहीं है)।

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


5
डबल के बजाय, अगर यह स्ट्रिंग के साथ व्यवहार करता है, तब भी मामला "बी" बेहतर है?
Antoops

3
@ एंटॉप्स - हां, बी उन कारणों के लिए बेहतर है, जिनका घोषित किए गए चर के डेटा प्रकार से कोई लेना-देना नहीं है। यह स्ट्रिंग्स के लिए अलग क्यों होगा?
डैनियल ईयरविकर

215

वैसे मैंने आपके ए और बी उदाहरणों को प्रत्येक 20 बार, 100 मिलियन बार लूप किया। (JVM - 1.5.0)

ए: औसत निष्पादन समय: .074 सेकंड

बी: औसत निष्पादन समय: .067 सेकंड

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


12
आपने मुझे हरा दिया मैं केवल प्रोफाइलिंग के लिए अपने परिणाम पोस्ट करने वाला था, मुझे कमोबेश वही मिला और हां आश्चर्यजनक रूप से बी वास्तव में तेजी से सोचा होगा अगर मुझे इस पर शर्त लगाने की आवश्यकता होती।
मार्क डेविडसन

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

142
+1 वास्तव में इसका परीक्षण करने के लिए , न कि केवल एक राय / सिद्धांत से ओपी स्वयं को बना सकता है।
MGOwen

3
@GoodPerson ईमानदार होने के लिए, मैं चाहता हूँ कि किया जाना चाहिए। मैंने लगभग 50,000 समान कोड के साथ 50,000,000-100,000,000 पुनरावृत्तियों के लिए अपनी मशीन पर लगभग 10 बार इस परीक्षण को चलाया (जो मैं आँकड़े चलाना चाहता हूं)। उत्तर लगभग समान रूप से या तो 900ms (50M से अधिक पुनरावृत्तियों पर) द्वारा विभाजित किए गए जो वास्तव में बहुत अधिक नहीं है। हालांकि मेरा पहला विचार यह है कि यह "शोर" होने वाला है, यह बस थोड़ा सा झुक सकता है। यह प्रयास मुझे विशुद्ध रूप से अकादमिक लगता है, हालांकि (अधिकांश वास्तविक जीवन अनुप्रयोगों के लिए) .. मैं वैसे भी परिणाम देखना पसंद करूंगा?) कोई भी सहमत है?
javatarz

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

66

यह भाषा और सटीक उपयोग पर निर्भर करता है। उदाहरण के लिए, C # 1 में इससे कोई फर्क नहीं पड़ा। C # 2 में, यदि स्थानीय चर को अनाम विधि (या C # 3 में लैम्ब्डा एक्सप्रेशन) द्वारा कैप्चर किया जाता है, तो यह बहुत ही सांकेतिक अंतर कर सकता है।

उदाहरण:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

आउटपुट:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

अंतर यह है कि सभी क्रियाएं समान outerचर को पकड़ती हैं, लेकिन प्रत्येक का अपना अलग innerचर है।


3
उदाहरण बी (मूल प्रश्न) में, क्या यह वास्तव में हर बार एक नया चर बनाता है? स्टैक की आँखों में क्या हो रहा है?
रॉय नमिर

@, क्या यह C # 1.0 में एक बग था? आदर्श रूप Outerसे 9 नहीं होना चाहिए ?
नवफाल

@nawfal: मुझे नहीं पता कि तुम्हारा क्या मतलब है। लैम्ब्डा भाव 1.0 में नहीं थे ... और बाहरी है 9. क्या बग मतलब है?
जॉन स्कीट

@nffal: मेरा कहना है कि C # 1.0 में कोई भाषा सुविधाएँ नहीं थीं, जहाँ आप एक लूप के अंदर एक चर घोषित करने और इसे बाहर घोषित करने (दोनों को संकलित मानकर) के बीच अंतर बता सकते थे। वह C # 2.0 में बदल गया। कोई बग नहीं।
जॉन स्कीट

@JonSkeet ओह हाँ, मैं तुम्हें अब मिल, मैं पूरी तरह से इस तथ्य को नजरअंदाज कर दिया कि आप 1.0 में वैरिएबल की तरह बंद कर सकते हैं, मेरा बुरा! :)
नवफाल

35

निम्नलिखित है जो मैंने .NET में लिखा और संकलित किया है।

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

जब CIL को कोड में वापस किया जाता है तो मुझे .NET रिफलेक्टर से यह मिलता है ।

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

इसलिए दोनों संकलन के बाद बिल्कुल एक जैसे दिखते हैं। प्रबंधित भाषाओं में कोड सीएल / बाइट कोड में बदल जाता है और निष्पादन के समय इसे मशीन की भाषा में बदल दिया जाता है। तो मशीन भाषा में स्टैक पर एक डबल भी नहीं बनाया जा सकता है। यह सिर्फ एक रजिस्टर हो सकता है क्योंकि कोड प्रतिबिंबित करता है कि यह WriteLineफ़ंक्शन के लिए एक अस्थायी चर है। केवल लूप के लिए पूरे सेट अनुकूलन नियम हैं। इसलिए औसत आदमी को इसके बारे में चिंतित नहीं होना चाहिए, विशेष रूप से प्रबंधित भाषाओं में। ऐसे मामले हैं जब आप प्रबंधन कोड को अनुकूलित कर सकते हैं, उदाहरण के लिए, यदि आपको बड़ी संख्या में स्ट्रिंग का उपयोग करना है, तो string a; a+=anotherstring[i]बनाम का उपयोग करकेStringBuilder। दोनों के प्रदर्शन में बहुत बड़ा अंतर है। ऐसे बहुत से मामले हैं जहां कंपाइलर आपके कोड को ऑप्टिमाइज़ नहीं कर सकता है, क्योंकि यह पता नहीं लगा सकता है कि बड़े दायरे में क्या इरादा है। लेकिन यह आपके लिए बुनियादी चीजों को बहुत ज्यादा अनुकूलित कर सकता है।


int j = 0 for (; j <0x3e8; j ++) इस तरह से एक बार दोनों चर घोषित किया गया, और प्रत्येक चक्र के लिए नहीं। 2) असाइनमेंट यह अन्य सभी विकल्प के लिए थीनर है। 3) तो सबसे अच्छा नियम नियम के लिए चलना के बाहर किसी भी घोषणा है।
लुका

24

यह VB.NET में एक गोच है। विजुअल बेसिक परिणाम इस उदाहरण में चर को फिर से संगठित नहीं करेगा:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

यह पहली बार 0 छपेगा (घोषित होने पर Visual Basic चर में डिफ़ॉल्ट मान हैं!) लेकिन iउसके बाद हर बार।

यदि आप एक जोड़ते हैं = 0, हालांकि, आपको वह मिलता है जो आप उम्मीद कर सकते हैं:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

1
मैं वर्षों के लिए VB.NET का उपयोग कर रहा हूँ और इस पार नहीं आया था !!
क्रिस

12
हां, व्यवहार में इसका पता लगाना अप्रिय है।
माइकल हरेन

यहाँ पॉल विक से इसका एक संदर्भ है: panopticoncentral.net/archive/2006/03/28/11552.aspx
ferventcoder

1
@eschneider @ferventcoder दुर्भाग्य से @PaulV ने अपने पुराने ब्लॉग पोस्ट को छोड़ने का फैसला किया है , इसलिए यह अब एक मृत लिंक है।
मार्क हर्ड

हाँ, अभी हाल ही में इस पार; इस पर कुछ आधिकारिक डॉक्स की तलाश थी ...
एरिक श्नाइडर

15

मैंने एक साधारण परीक्षण किया:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

बनाम

for (int i = 0; i < 10; i++) {
    int b = i;
}

मैंने इन कोड्स को gcc - 5.2.0 के साथ संकलित किया है। और फिर मैंने इन दोनों कोडों में से मुख्य () को अलग कर दिया और इसका परिणाम है:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

बनाम

2 º

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

जो एक ही asm परिणाम exaclty हैं। इस बात का प्रमाण नहीं है कि दो कोड एक ही चीज़ का उत्पादन करते हैं?


3
हाँ, और यह अच्छा है कि आपने ऐसा किया है, लेकिन यह वापस आता है जो लोग भाषा / संकलक निर्भरता के बारे में कह रहे थे। मुझे आश्चर्य है कि JIT या व्याख्यायित भाषा का प्रदर्शन कैसे प्रभावित होगा।
user137717

12

यह भाषा पर निर्भर है - IIRC C # इस का अनुकूलन करता है, इसलिए इसमें कोई अंतर नहीं है, लेकिन जावास्क्रिप्ट (उदाहरण के लिए) प्रत्येक बार पूरे मेमोरी आवंटन को करेगा।


हाँ, लेकिन यह बहुत अधिक राशि नहीं है। मैंने 100 मिलियन बार निष्पादित पाश के लिए एक सरल परीक्षण चलाया और मैंने पाया कि लूप के बाहर घोषित करने के पक्ष में सबसे बड़ा अंतर 8 एमएस था। यह आमतौर पर 3-4 की तरह अधिक होता था और कभी-कभी लूप के बाहर घोषित करने से WORSE (4 एमएस तक) का प्रदर्शन होता था, लेकिन यह विशिष्ट नहीं था।
user137717

11

मैं हमेशा A (कंपाइलर पर निर्भर रहने के बजाय) का उपयोग करूंगा और इसके लिए फिर से लिख सकता हूं:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

यह अभी भी intermediateResultलूप के दायरे को सीमित करता है, लेकिन प्रत्येक पुनरावृत्ति के दौरान पुन: लोड नहीं करता है।


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

4
आह, अच्छा समझौता, मैंने ऐसा कभी नहीं सोचा था! IMO, कोड थोड़ा कम नेत्रहीन 'स्पष्ट' हो जाता है)
Rabarberski

2
@ जॉन - मुझे नहीं पता कि ओपी वास्तव में मध्यवर्ती मूल्य के साथ क्या कर रहा है। बस यह विचार करने लायक विकल्प था।
१४:४४

6

मेरी राय में, बी बेहतर संरचना है। आपके लूप के ख़त्म होने के बाद, उसके बीच में, मध्यवर्ती मूल्य का अंतिम मान चिपक जाता है।

संपादित करें: यह मान प्रकार के साथ बहुत अंतर नहीं करता है, लेकिन संदर्भ प्रकार कुछ वजनदार हो सकते हैं। निजी तौर पर, मुझे क्लीनअप के लिए वेरिएबल्स जितनी जल्दी हो सके उतने ही पसंद हैं, और बी आपके लिए ऐसा करता है,


sticks around after your loop is finished- हालांकि यह पायथन जैसी भाषा में कोई फर्क नहीं पड़ता है, जहां बाध्य नाम फ़ंक्शन समाप्त होने तक चारों ओर चिपके रहते हैं।
new123456

@ new123456: ओपी ने जावा बारीकियों के लिए कहा, भले ही सवाल कुछ उदारता से पूछा गया हो । कई सी-व्युत्पन्न भाषाओं में ब्लॉक-स्तरीय स्कूपिंग है: सी, सी ++, पर्ल ( myकीवर्ड के साथ ), सी #, और जावा के लिए 5 मैंने उपयोग किया है।
पॉवरलॉर्ड

मुझे पता है - यह एक अवलोकन था, आलोचना नहीं।
new123456

5

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


5

एक सामान्य नियम के रूप में, मैं अपने चर को सबसे आंतरिक संभव दायरे में घोषित करता हूं। इसलिए, यदि आप लूप के बाहर मध्यवर्ती का उपयोग नहीं कर रहे हैं, तो मैं बी के साथ जाऊंगा।


5

एक सहकर्मी पहले फॉर्म को प्राथमिकता देता है, यह बताता है कि यह एक अनुकूलन है, एक घोषणा को फिर से उपयोग करना पसंद करता है।

मैं दूसरा पसंद करता हूं (और अपने सहकर्मी को मनाने की कोशिश करता हूं!; ;-)), पढ़ रहा हूं कि:

  • यह चर की गुंजाइश कम कर देता है जहां उन्हें जरूरत है, जो एक अच्छी बात है।
  • जावा प्रदर्शन में कोई महत्वपूर्ण अंतर नहीं लाने के लिए पर्याप्त अनुकूलन करता है। IIRC, शायद दूसरा रूप और भी तेज है।

वैसे भी, यह समय से पहले अनुकूलन की श्रेणी में आता है जो संकलक और / या जेवीएम की गुणवत्ता में भरोसा करते हैं।


5

C # में एक अंतर है यदि आप लैम्बडा आदि में वेरिएबल का उपयोग कर रहे हैं, लेकिन सामान्य तौर पर कंपाइलर मूल रूप से एक ही काम करेगा, यह मानते हुए कि वेरिएबल केवल लूप के भीतर ही उपयोग किया जाता है।

यह देखते हुए कि वे मूल रूप से समान हैं: ध्यान दें कि संस्करण b पाठकों के लिए यह अधिक स्पष्ट करता है कि चर नहीं है, और लूप के बाद उपयोग नहीं किया जा सकता है। इसके अतिरिक्त, संस्करण बी बहुत अधिक आसानी से refactored है। संस्करण में लूप बॉडी को अपनी विधि में निकालना अधिक कठिन है। इसके अलावा, संस्करण बी आपको भरोसा दिलाता है कि इस तरह के रिफैक्टरिंग का कोई दुष्प्रभाव नहीं है।

इसलिए, मुझे कोई भी अंत करने के लिए annoys संस्करण, क्योंकि इसका कोई लाभ नहीं है और यह कोड के बारे में तर्क करने के लिए इसे और अधिक कठिन बनाता है ...


5

खैर, आप हमेशा इसके लिए एक गुंजाइश बना सकते हैं:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

इस तरह आप केवल एक बार वेरिएबल की घोषणा करते हैं, और जब आप लूप छोड़ देते हैं तो यह मर जाएगा।


4

मैंने हमेशा सोचा है कि यदि आप अपने पाश के अंदर अपने चरों को घोषित करते हैं तो आप स्मृति को बर्बाद कर रहे हैं। यदि आपके पास ऐसा कुछ है:

for(;;) {
  Object o = new Object();
}

तब न केवल प्रत्येक पुनरावृत्ति के लिए ऑब्जेक्ट को बनाने की आवश्यकता होती है, बल्कि प्रत्येक ऑब्जेक्ट के लिए एक नया संदर्भ आवंटित करने की आवश्यकता होती है। ऐसा लगता है कि यदि कचरा संग्रहकर्ता धीमा है, तो आपके पास झूलने वाले संदर्भ होंगे, जिन्हें साफ करने की आवश्यकता है।

हालाँकि, यदि आपके पास यह है:

Object o;
for(;;) {
  o = new Object();
}

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


3
प्रत्येक ऑब्जेक्ट के लिए एक नया संदर्भ आवंटित नहीं किया जाता है, भले ही संदर्भ को 'for'-loop के भीतर घोषित किया गया हो। दोनों मामलों में: 1) 'ओ' एक स्थानीय वैरिएबल है और फ़ंक्शन की शुरुआत में एक बार इसके लिए स्टैक स्पेस आवंटित किया जाता है। 2) प्रत्येक पुनरावृत्ति में बनाई गई एक नई वस्तु है। इसलिए प्रदर्शन में कोई अंतर नहीं है। कोड संगठन, पठनीयता और स्थिरता के लिए, लूप के भीतर संदर्भ की घोषणा करना बेहतर है।
अजोना भाटिया

1
जब मैं जावा के लिए नहीं बोल सकता, .NET में उदाहरण में प्रत्येक ऑब्जेक्ट के लिए 'आवंटित' नहीं किया गया है। स्थानीय (विधि के लिए) चर के लिए स्टैक पर एक एकल प्रविष्टि है। आपके उदाहरणों के लिए, बनाया गया IL समान है।
जेसी सी। स्लीकर

3

मुझे लगता है कि यह कंपाइलर पर निर्भर करता है और सामान्य उत्तर देना कठिन है।


3

मेरा अभ्यास निम्नलिखित है:

  • यदि वेरिएबल का प्रकार सरल है (इंट, डबल, ...) मैं वेरिएंट बी (अंदर) पसंद करता हूं ।
    कारण: चर का दायरा कम करना।

  • यदि चर के प्रकार के सरल नहीं है (किसी तरह का classया struct) मैं संस्करण पसंद करते हैं एक (बाहर)।
    कारण: Ctor-dtor कॉल की संख्या कम करना।


1

प्रदर्शन के दृष्टिकोण से, बाहर (बहुत) बेहतर है।

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

मैंने दोनों कार्यों को 1 बिलियन बार पूरा किया। बाहर () में 65 मिलीसेकंड लगे। अंदर () 1.5 सेकंड लगे।


2
तब डिबग को अडॉप्ट कर लिया गया होगा, हुह?
टॉमस प्रेज़ोडोडज़की

int j = 0 for (; j <0x3e8; j ++) इस तरह से एक बार दोनों चर घोषित किया गया, और प्रत्येक चक्र के लिए नहीं। 2) असाइनमेंट यह अन्य सभी विकल्प के लिए थीनर है। 3) तो सबसे अच्छा नियम नियम के लिए चलना के बाहर किसी भी घोषणा है।
लूका

1

अगर कोई दिलचस्पी है तो मैंने Node 4.0.0 के साथ JS के लिए परीक्षण किया। लूप के बाहर घोषित करने पर प्रति परीक्षण 100 मिलियन लूप पुनरावृत्तियों के साथ 1000 से अधिक परीक्षणों पर औसतन ~ .5 एमएस प्रदर्शन में सुधार हुआ। तो मैं कह रहा हूं कि आगे बढ़ो और इसे सबसे पठनीय / बनाए रखने योग्य तरीके से लिखो जो कि B, imo है। मैं अपना कोड एक फिडेल में रखूंगा, लेकिन मैंने प्रदर्शन-अब नोड मॉड्यूल का उपयोग किया। यहाँ कोड है:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)

0

A) B की तुलना में एक सुरक्षित दांव है ......... सोचिए अगर आप 'int' या 'float' के बजाय लूप में स्ट्रक्चर इनिशियलाइज़ कर रहे हैं तो क्या?

पसंद

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

आप निश्चित रूप से स्मृति लीक के साथ समस्याओं का सामना करने के लिए बाध्य हैं! इसलिए मेरा मानना ​​है कि 'ए' सुरक्षित दांव है जबकि 'बी' मेमोरी संचय एस्प के काम करने वाले करीबी स्रोत पुस्तकालयों के लिए असुरक्षित है। आप लिनक्स पर 'वेलग्राइंड' टूल की जांच कर सकते हैं विशेष रूप से उप उपकरण 'हेलग्रिंड'।


0

यह एक दिलचस्प सवाल है। मेरे अनुभव से एक अंतिम प्रश्न पर विचार करना है जब आप एक कोड के लिए इस मामले पर बहस करते हैं:

क्या कोई कारण है कि चर को वैश्विक होने की आवश्यकता होगी?

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

इसके अलावा, एक साइड नोट के रूप में, स्थानीय चर नामों को विभिन्न तरीकों के बीच डुप्लिकेट न करें, भले ही उनके उद्देश्य समान हों; यह सिर्फ भ्रमित करता है।


1
lol मैं इतने सारे कारणों से असहमत हूं ... हालांकि, वोट डाउन नहीं हुआ ... मैं आपका चयन करने के अधिकार का सम्मान करता हूं
धीरे

0

यह बेहतर रूप है

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) इस तरह से एक बार दोनों चर घोषित किया गया है, और प्रत्येक चक्र के लिए नहीं। 2) असाइनमेंट यह अन्य सभी विकल्प है। 3) तो सबसे अच्छा नियम नियम के लिए चलना के बाहर किसी भी घोषणा है।


0

गो में एक ही बात की कोशिश की, और go tool compile -S1.9.4 के साथ का उपयोग कर संकलक उत्पादन की तुलना की

संयोजन आउटपुट के अनुसार शून्य अंतर।


0

मेरे पास बहुत लंबे समय से एक ही सवाल था। इसलिए मैंने कोड का एक और भी सरल टुकड़ा परीक्षण किया।

निष्कर्ष: के लिए इस तरह के मामलों है कोई प्रदर्शन अंतर।

बाहर का लूप केस

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

लूप केस के अंदर

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

मैंने IntelliJ के डिकंपाइलर पर संकलित फ़ाइल की जाँच की और दोनों मामलों के लिए, मुझे वही मिला Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

मैंने इस उत्तर में दी गई विधि का उपयोग करते हुए दोनों केस के लिए कोड भी डिसाइड किया । मैं केवल उत्तर के लिए प्रासंगिक भागों को दिखाऊंगा

बाहर का लूप केस

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

लूप केस के अंदर

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

आप अधिक ध्यान दें, तो केवल Slotकरने के लिए सौंपा iऔर intermediateResultमें LocalVariableTableउपस्थिति के अपने आदेश की एक उत्पाद के रूप में बदली है। स्लॉट में समान अंतर कोड की अन्य लाइनों में परिलक्षित होता है।

  • कोई अतिरिक्त ऑपरेशन नहीं किया जा रहा है
  • intermediateResult दोनों मामलों में अभी भी एक स्थानीय चर है, इसलिए कोई अंतर नहीं है।

बक्शीश

कंपाइलर एक टन अनुकूलन करते हैं, इस मामले में क्या होता है, इस पर एक नज़र डालें।

शून्य काम का मामला

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

शून्य कार्य विघटित

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

-1

यहां तक ​​कि अगर मुझे पता है कि मेरा कंपाइलर काफी स्मार्ट है, तो मैं इस पर भरोसा नहीं करना चाहूंगा, और a) वेरिएंट का इस्तेमाल करूंगा।

ख) संस्करण मेरे लिए केवल तभी समझ में आता है, जब आपको लूप बॉडी के बाद इंटरमीडिएट की आवश्यकता नहीं होती है। लेकिन मैं ऐसी हताश स्थिति की कल्पना नहीं कर सकता, वैसे भी ...।

संपादित करें: जॉन स्कीट ने एक बहुत अच्छा बिंदु बनाया, यह दिखाते हुए कि लूप के अंदर परिवर्तनीय घोषणा वास्तविक वास्तविक अंतर बना सकती है।

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