2013-08-10 3 views
8

पुनरावृत्तियों की बड़ी संख्या के साथ एक खाली के लिए लूप चल रहा है, मैं कब तक इसे चलाने के लिए ले जाता है में बेतहाशा अलग नंबरों हो रही है:.NET x64 में भी-लूप प्रदर्शन विषमता: यहां तक ​​कि संख्या-पुनरावृत्ति संबंध भी?

public static class Program 
{ 
    static void Main() 
    { 
     var sw = new Stopwatch(); 
     sw.Start(); 
     for (var i = 0; i < 1000000000; ++i) 
     { 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

ऊपर में मेरी मशीन पर चारों ओर 200 मि.से चलाया जाएगा, लेकिन अगर मैं इसे 1000000001 तक बढ़ाएं, फिर यह 4x लेता है! तो अगर मैं इसे 1000000002 बना देता हूं, तो यह फिर से 200ms तक नीचे है!

यह लगता है कि पुनरावृत्तियों की संख्या भी होती है। अगर मैं for (var i = 1; i < 1000000001 पर जाता हूं, (नोट 0 के बजाय 1 से शुरू होता है) तो यह 200ms है। या यदि मैं i <= 1000000001 (से कम या के बराबर नोट) तो यह 200ms है। या (var i = 0; i < 2000000000; i += 2) भी।

यह केवल x64 पर दिखाई देता है, लेकिन सभी .NET संस्करणों (कम से कम) 4.0 तक। यह तब भी दिखाई देता है जब डिबगर के साथ रिहाई मोड में अलग हो जाता है।

अद्यतन मैं सोच रहा था कि इस वजह से कुछ चतुर बिट JIT में स्थानांतरण, लेकिन निम्न की संभावना थी कि खंडन लगता है: यदि आप तो है कि लूप के अंदर एक वस्तु बनाने के लिए, की तरह कुछ करना कि के बारे में लेता है 4x रूप में लंबे समय भी:

public static class Program 
{ 
    static void Main() 
    { 
     var sw = new Stopwatch(); 
     sw.Start(); 
     object o = null; 
     for (var i = 0; i < 1000000000; i++) 
     { 
      o = new object(); 
     } 
     sw.Stop(); 
     Console.WriteLine(o); // use o so the compiler won't optimize it out 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

यह मेरा मशीन पर चारों ओर 1 सेकंड लेता है, लेकिन फिर 1000000001 करने के लिए 1 से बढ़ रही यह 4 सेकंड लेता है। यह एक अतिरिक्त 3000 एमएमएस है, इसलिए यह वास्तव में थोड़ा स्थानांतरण के कारण नहीं हो सका, क्योंकि मूल समस्या में 3000 एमएमएस अंतर के रूप में भी दिखाया गया होगा।

+0

शायद यह लूप के दो पुनरावृत्तियों unrolls अगर सीमा भी है और उसके बाद पता चलता है कि यात्रा की पहली छमाही के परिणाम कभी उपयोग नहीं किया और इसे बाहर का अनुकूलन। – CodesInChaos

उत्तर

6

खैर यहाँ disassemblies हैं:

00000031 xor   eax,eax 
    for (var i = 0; i < 1000000001; ++i) 
00000033 inc   eax   
00000035 cmp   eax,3B9ACA01h 
0000003a jl   0000000000000033 
0000003c movzx  eax,byte ptr [rbx+18h] 
00000040 test  eax,eax 
00000042 je   0000000000000073 

और

00000031 xor   eax,eax 
    for (var i = 0; i < 1000000000; ++i) 
00000033 add   eax,4 
00000036 cmp   eax,3B9ACA00h 
0000003b jl   0000000000000033 
0000003d movzx  eax,byte ptr [rbx+18h] 
00000041 test  eax,eax 
00000043 je   0000000000000074 

फर्क सिर्फ इतना है मुझे लगता है कि यहां तक ​​कि पाश में, पाश सूचकांक एक समय में 4 से वृद्धि की जाती है है (add eax 4) एक समय में 1 के बजाय (inc eax) ताकि यह लूप 4x तेज हो क्योंकि इसकी वजह से।

यह सिर्फ अटकलें है लेकिन मेरा मानना ​​है कि यह unrolling the loop 4 के कारक से है। इसलिए यह लूप के अंदर शरीर को 4 बार रखता है और केवल 4 गुना तेजी से बढ़ता है। लेकिन क्योंकि शरीर खाली है, खाली शरीर के समय 4 अभी भी खाली हैं, आप लूप अनोलिंग से अपेक्षा करते हुए बहुत अधिक लाभ प्राप्त करते हैं।

+0

आप डिस्सेप्लर कैसे देखते हैं? – lobsterism

+2

stackoverflow.com/questions/3423547/how-can-i-view-the-disassembly-of-optimised-jitted-net-code – Esailija

+3

हां, यह काम पर लूप अनलॉकिंग है। यह [ऑप्टिमाइज़ेशन] खराब होने के बारे में [इस उत्तर] (http://stackoverflow.com/a/2057228/17034) में अधिक दिखाई देता है। एक बेहतर अनुकूलक इसे दो वर्गों में विभाजित करता है, जो अनलॉक किया जाता है और दूसरा जो पिछले कुछ पुनरावृत्तियों का ख्याल रखता है। लेकिन जिटर ऑप्टिमाइज़र के पास आउटलाइनर्स को काम करने के लिए पर्याप्त समय नहीं है। –

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