2017-08-29 5 views
10

कोड: समान usings मेंउसी कोड ब्लॉक निष्पादन के इतने अलग अवधि का कारण क्या है?

internal class Program 
{ 
    private static void Main(string[] args) 
    { 
     const int iterCount = 999999999; 

     var sum1 = 0; 
     var sum2 = 0; 

     using (new Dis()) 
     { 
      var sw = DateTime.Now; 
      for (var i = 0; i < iterCount; i++) 
       sum1 += i; 
      Console.WriteLine(sum1); 
      Console.WriteLine(DateTime.Now - sw); 
     } 

     using (new Dis()) 
     { 
      var sw = DateTime.Now; 
      for (var i = 0; i < iterCount; i++) 
       sum2 += i; 
      Console.WriteLine(sum2); 
      Console.WriteLine(DateTime.Now - sw); 
     } 

     Console.ReadLine(); 
    } 

    private class Dis : IDisposable 
    { 
     public void Dispose(){} 
    } 
} 

दो समान ब्लॉकों।

आउटपुट:

2051657985 
00:00:00.3690996 
2051657985 
00:00:02.2640266 

दूसरा ब्लॉक 2.2 सेकंड लेता है! लेकिन अगर उपयोग से छुटकारा पाने के लिए, अवधि समान हो गई (~ 0.3s, पहले की तरह)। मैंने रिलीज में .NET फ्रेमवर्क 4.5 और .NET कोर 1.1 के साथ प्रयास किया है, परिणाम समान हैं।

क्या कोई उस व्यवहार को समझा सकता है?

+2

शायद इसे 'कंसोल। राइटलाइन' के बजाय चर के साथ आज़माएं?यह एक समस्या हो सकती है। – ispiro

+0

जब मैं आपका कोड linqpad में चलाता हूं तो यह मुझे कोड ब्लॉक दोनों के लिए ~ 2.5 सेकंड देता है। जब विचार पर चलाया जाता है तो यह दोनों ब्लॉक के लिए ~ 0.3 सेकंड देता है: https://ideone.com/ReOpaH। – Chris

+2

एक उचित स्टॉपवॉच का भी उपयोग करें। आप अपने डेटाटाइम वेरिएबल sw को कॉल करते हैं जिसका अर्थ है कि आप स्टॉपवॉच का उपयोग करना चाहते हैं लेकिन फिर आप एक का उपयोग नहीं कर रहे हैं। – Chris

उत्तर

13

आपको उस मशीन कोड को देखना होगा जो जिटर अंतर्निहित कारण को देखने के लिए उत्पन्न करता है। उपकरण> विकल्प> डिबगिंग> सामान्य> दबाने का समर्थन करें JIT ऑप्टिमाइज़ेशन विकल्प को अनचेक करें। रिलीज बिल्ड पर स्विच करें। पहले और दूसरे लूप पर ब्रेकपॉइंट सेट करें। जब यह डीबग> विंडोज> Disassembly का उपयोग हिट करता है।

आप पाश के लिए के शव के लिए मशीन कोड देखेंगे:

    sum1 += i; 
00000035 add   esi,eax 

और:

    sum2 += i; 
000000d9 add   dword ptr [ebp-24h],eax 

या दूसरे शब्दों में, sum1 चर सीपीयू रजिस्टर esi में संग्रहित है। लेकिन sum2 चर विधि में ढेर फ्रेम पर स्मृति में संग्रहीत किया जाता है। बड़ा, बड़ा अंतर। रजिस्टर्स बहुत तेज हैं, स्मृति धीमी है। स्टैक फ्रेम के लिए स्मृति एल 1 कैश में होगी, आधुनिक मशीनों पर उस कैश तक पहुंचने पर 3 चक्रों की विलम्ब होती है। स्टोर बफर बड़ी संख्या में लिखने के साथ जल्दी से अभिभूत हो जाएगा और प्रोसेसर को रोकने का कारण बनता है।

सीपीयू रजिस्टरों में चर रखने के लिए एक रास्ता ढूंढना one of the primary jitter optimization duties है। लेकिन इसमें सीमाएं हैं, विशेष रूप से x86 में कुछ रजिस्ट्रार उपलब्ध हैं। जब वे सभी का उपयोग किया जाता है तो जिटर के पास स्मृति का उपयोग करने के अलावा कोई विकल्प नहीं होता है। ध्यान दें कि using कथन में हुड के नीचे एक अतिरिक्त छुपा स्थानीय चर है, इसलिए इसका प्रभाव पड़ा।

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

बेसिक जवाबी उपाय कर रहे हैं:

  • अलग तरीकों में कोड तो ईएसआई की तरह एक रजिस्टर फिर से इस्तेमाल किया जा सकता ऊपर तोड़।
  • जिटर फोर्सिंग को हटाएं (प्रोजेक्ट> गुण> टैब बनाएं> अनटिक करें "32-बिट पसंद करें")। x64 8 अतिरिक्त रजिस्ट्रार प्रदान करता है।

अंतिम गोली विरासत x64 जिटर (लक्ष्य .NET 3.5 का उपयोग करने के लिए) के लिए प्रभावी है, लेकिन x64 जिटर रीराइट (उर्फ आरयूयूजेआईटी) के लिए पहले 4.6 में उपलब्ध नहीं है। एक पुनः लिखना जरूरी था क्योंकि विरासत जिटर ने कोड को अनुकूलित करने में बहुत अधिक समय लगाया था। निराशाजनक, RyuJIT निराशाजनक के लिए एक नाटक है, मुझे लगता है कि इसका अनुकूलक यहां बेहतर काम कर सकता है।

+0

धन्यवाद, उत्तर की तरह दिखता है! दिलचस्प बात यह है कि अगर हम ब्लॉक का उपयोग करने के बीच sum2 (var sum2 = 0;) की घोषणा को स्थानांतरित करते हैं, तो दोनों लूप में रजिस्टरों का उपयोग किया जाता है। –

संबंधित मुद्दे