फ़ॉरच लूप और वैरिएबल इनिशियलाइज़ेशन


11

क्या कोड के इन दो संस्करणों में अंतर है?

foreach (var thing in things)
{
    int i = thing.number;
    // code using 'i'
    // pay no attention to the uselessness of 'i'
}

int i;
foreach (var thing in things)
{
    i = thing.number;
    // code using 'i'
}

या कंपाइलर की परवाह नहीं है? जब मैं अंतर की बात कर रहा हूं तो मेरा मतलब प्रदर्शन और स्मृति उपयोग के संदर्भ में है। .. या मूल रूप से सिर्फ कोई अंतर है या दो अंत संकलन के बाद एक ही कोड है?


6
क्या आपने दोनों को संकलित करने की कोशिश की है और बाईटकोड आउटपुट को देख रहे हैं?

4
@ मिचेल्ट मुझे ऐसा नहीं लगता है कि मैं बायटेकोड आउटपुट की तुलना करने के लिए योग्य हूं। यदि मुझे ऐसा अंतर लगता है तो मुझे यकीन नहीं है कि मैं समझ पाऊंगा कि इसका क्या मतलब है।
अल्टरनेटेक्स

4
यदि इसका समान है, तो आपको योग्य होने की आवश्यकता नहीं है।

1
@MichaelT हालांकि आपको इस बारे में एक अच्छा अनुमान लगाने के लिए पर्याप्त योग्य होने की आवश्यकता है कि क्या कंपाइलर इसे अनुकूलित कर सकता है, और यदि ऐसा है तो किन परिस्थितियों में यह अनुकूलन करने में सक्षम है।
बेन आरोनसन

@BenAaronson और उस कार्यक्षमता को गुदगुदाने के लिए गैर-तुच्छ उदाहरण की आवश्यकता है।

जवाबों:


22

टीएल; डीआर - वे आईएल परत पर समान उदाहरण हैं।


DotNetFiddle यह जवाब देने के लिए बहुत सुंदर बनाता है क्योंकि यह आपको परिणामी आईएल को देखने की अनुमति देता है।

अपने परीक्षण को तेज करने के लिए मैंने आपके लूप निर्माण की थोड़ी भिन्नता का उपयोग किया। मैंनें इस्तेमाल किया:

विविधता 1:

using System;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");
        int x;
        int i;

        for(x=0; x<=2; x++)
        {
            i = x;
            Console.WriteLine(i);
        }
    }
}

विविधता 2:

        Console.WriteLine("Hello World");
        int x;

        for(x=0; x<=2; x++)
        {
            int i = x;
            Console.WriteLine(i);
        }

दोनों ही मामलों में, संकलित IL आउटपुट ने समान रूप से प्रस्तुत किया।

.class public auto ansi beforefieldinit Program
       extends [mscorlib]System.Object
{
  .method public hidebysig static void  Main() cil managed
  {
    // 
    .maxstack  2
    .locals init (int32 V_0,
             int32 V_1,
             bool V_2)
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World"
    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  ldc.i4.0
    IL_000d:  stloc.0
    IL_000e:  br.s       IL_001f

    IL_0010:  nop
    IL_0011:  ldloc.0
    IL_0012:  stloc.1
    IL_0013:  ldloc.1
    IL_0014:  call       void [mscorlib]System.Console::WriteLine(int32)
    IL_0019:  nop
    IL_001a:  nop
    IL_001b:  ldloc.0
    IL_001c:  ldc.i4.1
    IL_001d:  add
    IL_001e:  stloc.0
    IL_001f:  ldloc.0
    IL_0020:  ldc.i4.2
    IL_0021:  cgt
    IL_0023:  ldc.i4.0
    IL_0024:  ceq
    IL_0026:  stloc.2
    IL_0027:  ldloc.2
    IL_0028:  brtrue.s   IL_0010

    IL_002a:  ret
  } // end of method Program::Main

तो आपके प्रश्न का उत्तर देने के लिए: संकलक चर की घोषणा का अनुकूलन करता है, और दो भिन्नताओं को बराबर करता है।

मेरी समझ से, .NET IL कंपाइलर फ़ंक्शन की शुरुआत में सभी चर घोषणाओं को आगे बढ़ाता है, लेकिन मुझे एक अच्छा स्रोत नहीं मिला जो स्पष्ट रूप से कहा गया है कि 2 । इस विशेष उदाहरण में, आप देखते हैं कि इसने उन्हें इस कथन के साथ आगे बढ़ाया:

    .locals init (int32 V_0,
             int32 V_1,
             bool V_2)

जिसमें हमें तुलना करने में थोड़ा जुनूनी हो जाता है ...।

केस ए, क्या सभी चर बढ़ गए हैं?

इसे थोड़ा और खोदने के लिए, मैंने निम्नलिखित फ़ंक्शन का परीक्षण किया:

public static void Main()
{
    Console.WriteLine("Hello World");
    int x=5;

    if (x % 2==0) 
    { 
        int i = x; 
        Console.WriteLine(i); 
    }
    else 
    { 
        string j = x.ToString(); 
        Console.WriteLine(j); 
    } 
}

यहाँ अंतर यह है कि हम या तो तुलना के आधार पर int iया तो घोषित करते हैं string j। फिर से, कंपाइलर सभी स्थानीय चर को फ़ंक्शन 2 के शीर्ष पर ले जाता है:

.locals init (int32 V_0,
         int32 V_1,
         string V_2,
         bool V_3)

मुझे यह ध्यान रखना दिलचस्प है कि भले ही int iइस उदाहरण में घोषित नहीं किया जाएगा, फिर भी समर्थन करने के लिए कोड अभी भी उत्पन्न होता है।

केस बी: foreachइसके बजाय क्या होगा for?

यह बताया गया था कि foreachइसकी तुलना में अलग व्यवहार है forऔर मैं उसी चीज़ की जाँच नहीं कर रहा था जिसके बारे में पूछा गया था। इसलिए मैंने परिणामी IL की तुलना करने के लिए कोड के इन दो वर्गों में रखा।

int लूप के बाहर घोषणा:

    Console.WriteLine("Hello World");
    List<int> things = new List<int>(){1, 2, 3, 4, 5};
    int i;

    foreach(var thing in things)
    {
        i = thing;
        Console.WriteLine(i);
    }

int लूप के अंदर की घोषणा:

    Console.WriteLine("Hello World");
    List<int> things = new List<int>(){1, 2, 3, 4, 5};

    foreach(var thing in things)
    {
        int i = thing;
        Console.WriteLine(i);
    }

foreachलूप के परिणामस्वरूप आईएल वास्तव में लूप का उपयोग करके उत्पन्न आईएल से अलग था for। विशेष रूप से, इनिट ब्लॉक और लूप सेक्शन बदल गया।

.locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0,
         int32 V_1,
         int32 V_2,
         class [mscorlib]System.Collections.Generic.List`1<int32> V_3,
         valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> V_4,
         bool V_5)
...
.try
{
  IL_0045:  br.s       IL_005a

  IL_0047:  ldloca.s   V_4
  IL_0049:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
  IL_004e:  stloc.1
  IL_004f:  nop
  IL_0050:  ldloc.1
  IL_0051:  stloc.2
  IL_0052:  ldloc.2
  IL_0053:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0058:  nop
  IL_0059:  nop
  IL_005a:  ldloca.s   V_4
  IL_005c:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
  IL_0061:  stloc.s    V_5
  IL_0063:  ldloc.s    V_5
  IL_0065:  brtrue.s   IL_0047

  IL_0067:  leave.s    IL_0078

}  // end .try
finally
{
  IL_0069:  ldloca.s   V_4
  IL_006b:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
  IL_0071:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
  IL_0076:  nop
  IL_0077:  endfinally
}  // end handler

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

लेकिन forऔर foreachनिर्माणों के उपयोग के कारण होने वाली शाखाओं के अंतर से परे , आईएल के आधार पर कोई अंतर नहीं था जहां int iघोषणा की गई थी। इसलिए हम अभी भी दो दृष्टिकोणों के समतुल्य हैं।

केस सी: विभिन्न संकलक संस्करणों के बारे में क्या?

एक टिप्पणी जो 1 छोड़ दी गई थी , उसमें एसओ के लिए एक लिंक था जिसमें फ़ॉरच के साथ परिवर्तनीय पहुंच के बारे में चेतावनी दी गई थी और बंद करने का उपयोग किया गया था । उस प्रश्न में मेरी आंख को पकड़ने वाला हिस्सा यह था कि .NET 4.5 संकलक ने बनाम संकलक के पूर्व संस्करणों में काम करने के तरीके में अंतर हो सकता है।

और यहीं से DotNetFiddler साइट ने मुझे निराश किया - उनके पास जो भी उपलब्ध था वह था .NET 4.5 और रोसलिन कंपाइलर का एक संस्करण। इसलिए मैंने विज़ुअल स्टूडियो का एक स्थानीय उदाहरण पेश किया और कोड का परीक्षण शुरू किया। यह सुनिश्चित करने के लिए कि मैं समान चीजों की तुलना कर रहा था, मैंने .NET 4.5 पर स्थानीय रूप से निर्मित कोड की तुलना डॉटनेटफीडलर कोड से की।

एकमात्र अंतर जो मैंने नोट किया, वह स्थानीय इनिट ब्लॉक और वेरिएबल डिक्लेरेशन के साथ था। चरों के नामकरण में स्थानीय कंपाइलर थोड़ा अधिक विशिष्ट था।

  .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> things,
           [1] int32 thing,
           [2] int32 i,
           [3] class [mscorlib]System.Collections.Generic.List`1<int32> '<>g__initLocal0',
           [4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> CS$5$0000,
           [5] bool CS$4$0001)

लेकिन उस मामूली अंतर के साथ, यह इतना अच्छा था। मेरे पास DotNetFiddler संकलक के बीच समान आईएल आउटपुट था और मेरा स्थानीय वीएस उदाहरण क्या पैदा कर रहा था।

इसलिए मैंने फिर .NET 4, .NET 3.5, और अच्छे उपाय .NET 3.5 रिलीज मोड के लिए लक्ष्यीकरण परियोजना को फिर से बनाया।

और उन तीन अतिरिक्त मामलों में, उत्पन्न IL बराबर था। इन नमूनों में उत्पन्न IL पर लक्षित .NET संस्करण का कोई प्रभाव नहीं पड़ा।


इस साहसिक कार्य को संक्षेप में प्रस्तुत करने के लिए: मुझे लगता है कि हम आत्मविश्वास से कह सकते हैं कि संकलक को परवाह नहीं है कि आप आदिम प्रकार की घोषणा कहां करते हैं और यह कि घोषणा पद्धति के साथ स्मृति या प्रदर्शन पर कोई प्रभाव नहीं पड़ता है। और यह सच है कि एक forया foreachपाश का उपयोग करने की परवाह किए बिना ।

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

1 छोरों के भीतर बंद होने वाले एसओ प्रश्न को मूल लिंक प्रदान करने के लिए एंडी को धन्यवाद foreach

2 यह ध्यान देने योग्य है कि ECMA-335 ने इसे I.12.3.2.2 खंड 'स्थानीय चर और तर्कों' के साथ संबोधित किया है। मुझे परिणामी IL को देखना था और फिर जो कुछ चल रहा था उसके बारे में स्पष्ट होने के लिए अनुभाग पढ़ें। चैट में यह इंगित करने के लिए शाफ़्ट फ्रीक का धन्यवाद।


1
के लिए और foreach समान व्यवहार नहीं करते हैं, और प्रश्न में कोड शामिल होता है जो अलग होता है जो लूप में बंद होने पर महत्वपूर्ण हो जाता है। stackoverflow.com/questions/14907987/…
एंडी

1
@Andy - लिंक के लिए धन्यवाद! मैंने आगे बढ़कर एक foreachलूप का उपयोग करके उत्पन्न आउटपुट की जाँच की और लक्षित .NET संस्करण की भी जाँच की।

0

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

यदि आप प्रत्येक बार एक ही चर को एक प्रारंभिक रूप से परिभाषित कर रहे थे, तो संकलक इसे लूप से पहले आरंभीकरण करेगा और इसे संदर्भित करेगा।

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


3
आपका अंतिम पैराग्राफ सही है या नहीं, यह दो बातों पर निर्भर करता है: अपने स्वयं के प्रोग्राम के अनूठे संदर्भ के भीतर चर के दायरे को कम करने का महत्व, और कंपाइलर के अंदर के ज्ञान के रूप में कि क्या यह वास्तव में कई असाइनमेंट का अनुकूलन करता है या नहीं।
रॉबर्ट हार्वे

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

-2

पहले तो आप सिर्फ लूप के अंदर की घोषणा कर रहे हैं और हर बार कर रहे हैं, इसलिए हर बार लूप लूप्स को लूप के अंदर "i" को फिर से संगठित किया जाएगा। दूसरे में आप केवल लूप के बाहर की घोषणा कर रहे हैं।


1
ऐसा लगता है कि 2 साल पहले पोस्ट किए गए शीर्ष उत्तर में बताए गए बिंदुओं पर पर्याप्त मात्रा में कुछ भी नहीं दिया गया है
gnat

2
उत्तर देने के लिए धन्यवाद, लेकिन यह किसी भी नए पहलुओं को स्वीकार नहीं करता है , शीर्ष रेटेड उत्तर पहले से ही कवर नहीं करता है (विस्तार से)।
चारोनएक्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.