2011-06-18 16 views
15

मुझे .NET में यह अजीब व्यवहार मिला और फिर भी CLR via C# में देखने के बाद भी मैं उलझन में हूं। मान लेते हैं कि हम एक विधि और एक वर्ग है कि imlements साथ एक अंतरफलक करते हैं यह:सी #: क्यों इंटरफ़ेस चर के लिए क्लास चर के लिए कार्यान्वित इंटरफ़ेस विधि कॉलिंग तेज है?

interface IFoo 
{ 
    void Do(); 
} 

class TheFoo : IFoo 
{ 
    public void Do() 
    { 
     //do nothing 
    } 
} 

फिर हम सिर्फ इस वर्ग का दृष्टांत और यह करने के() विधि को दो तरह से समय की एक बहुत कुछ कॉल करना चाहते हैं: ठोस वर्ग चर का उपयोग कर होगा

Elapsed time: 6005 
Elapsed time: 6667 

अंतर यह है कि बहुत ज्यादा नहीं है, इसलिए मैं नहीं: (कम से कम मेरे लिए)

TheFoo foo1 = new TheFoo(); 

Stopwatch stopwatch = new Stopwatch(); 
stopwatch.Start(); 
for (long i = 0; i < 1000000000; i++) 
    foo1.Do(); 
stopwatch.Stop(); 
Console.Out.WriteLine("Elapsed time: " + stopwatch.ElapsedMilliseconds); 

IFoo foo2 = foo1; 

stopwatch = new Stopwatch(); 
stopwatch.Start(); 
for (long i = 0; i < 1000000000; i++) 
    foo2.Do(); 
stopwatch.Stop(); 
Console.Out.WriteLine("Elapsed time: " + stopwatch.ElapsedMilliseconds); 

हैरानी की बात बीत बार% के बारे में 10 अलग-अलग हैं और एक अंतरफलक चर का उपयोग कर ज्यादातर मामलों में इस बारे में बहुत चिंता मत करो। हालांकि मैं यह नहीं समझ सकता कि आईएल कोड में देखने के बाद भी ऐसा क्यों होता है, इसलिए अगर कोई मुझे कुछ स्पष्ट करने के लिए इंगित करता है तो मैं सराहना करता हूं कि मुझे याद आ रही है।

+0

मैंने आपके परीक्षण चलाए और मुझे वास्तव में एक बहुत अलग परिणाम मिला।विलुप्त समय: 12125 विलुप्त समय: 11682. मैं स्पष्ट रूप से एक धीमी मशीन पर चल रहा हूं। इस तरह के प्रदर्शन को मापना कठिन है क्योंकि ऐसे कारक हैं जो खेल में हो सकते हैं जो स्पष्ट नहीं हैं। –

+1

@ क्रेग शायद आपके पास कुछ प्रक्रिया हस्तक्षेप है। मेरे पास 10 बार के लिए इस मिनी-बेंचमार्क पर बहुत ही लगातार परिणाम हैं। –

+0

@ इवान मैंने पोस्ट करने से पहले इसे कई बार चलाया और लगातार परिणाम प्राप्त किए। मैं बस गया और इसे लूप में डाल दिया और इसे 10 बार पहले वापस चला रहा था। मेरे पास मशीन पर बहुत कम चल रहा है और मैं लगातार पहले मामले को धीरे-धीरे देख रहा हूं। यह अब एक महत्वपूर्ण अंतर है, लगभग 2: 1। मैंने एक और वर्ग भी जोड़ा जो स्पष्ट रूप से इंटरफ़ेस को लागू करता है और उस पर परीक्षण चलाता है। यह दूसरे मामले के समान ही भाग गया। आप किस ढांचे का उपयोग कर रहे हैं? हो सकता है कि मतभेदों के लिए खाते हैं। –

उत्तर

17

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

जिटर द्वारा किए गए सामान्य अनुकूलन की सूची के लिए this answer देखें। उस उत्तर में उल्लिखित प्रोफाइलिंग के बारे में चेतावनियों पर ध्यान दें।

नोट: रिलीज बिल्ड में मशीन कोड को देखने के लिए एक विकल्प बदलने की आवश्यकता है। डिफ़ॉल्ट रूप से जब आप रिलीज बिल्ड में भी कोड डीबग करते हैं तो ऑप्टिमाइज़र अक्षम होता है। उपकरण + विकल्प, डिबगिंग, सामान्य, मॉड्यूल लोड पर जेआईटी ऑप्टिमाइज़ेशन दबाएं "अनटिक करें।

+0

चीज़ वास्तव में मैंने डीबगर में असेंबलर कोड के परिणामस्वरूप देखा है। यह जिटर द्वारा अनुकूलित नहीं किया गया था। लेकिन वैसे भी यह एक अच्छा मुद्दा है। +1 –

+0

अनुकूलन प्रभाव को बाहर करने के लिए मैंने केवल स्थिर int चर शामिल किया है जो प्रत्येक Do() कॉल पर बढ़ी है। और अंत में 'x' कंसोल पर जाता है, ताकि संकलक इसे भी खत्म नहीं कर सके। समय थोड़ा अधिक हो गया है लेकिन फिर भी प्रवृत्ति वही है। –

+0

@Ivan: * कैसे * आपने देखा कि परिणामस्वरूप कोड जिटर द्वारा अनुकूलित नहीं किया गया था? लोग अक्सर यह गलत करते हैं। आपको क्या करना है यह सुनिश्चित करना है कि * डीबगर संलग्न करने से पहले * कोड को जला दिया गया है। जिटर जानता है कि एक डीबगर संलग्न है या नहीं, और अनुकूलन के बारे में कम आक्रामक हो जाता है जो ब्रेकपॉइंट्स सेट करना मुश्किल बनाता है अगर यह जानता है कि एक डीबगर संलग्न है। –

1

ठीक है, संकलक सामान्य मामले में नहीं आ सकता है, जब इंटरफ़ेस विधि कहा जाता है तो वास्तविक विधि निकाय को निष्पादित किया जाना चाहिए क्योंकि विभिन्न वर्गों में अलग-अलग कार्यान्वयन हो सकते हैं।

तो, जब सीएलआर इंटरफ़ेस कॉल का सामना करता है तो यह संलग्न प्रकार के इंटरफ़ेस मैपिंग पर देखता है और जांचता है कि कौन सी ठोस विधि को कॉल करना चाहिए। यह वास्तव में आईएल से कम है।

यूपीडी: आईएमओ call और callvirt के बीच अंतर नहीं है।

क्लास प्रकार पर callvirt से मुकाबला करने पर सीएलआर क्या करना चाहिए? कैली का प्रकार प्राप्त करें, इसकी आभासी विधियों की तालिका देखें, वहां विधि को बुलाएं और इसे कॉल करें।

यह इंटरफ़ेस प्रकार पर callvirt से मुकाबला करने पर क्या करना चाहिए? खैर, पिछले बिंदुओं के अलावा इसे स्पष्ट इंटरफ़ेस कार्यान्वयन के रूप में ऐसी चीजों को भी देखना चाहिए। क्योंकि आपके पास समान हस्ताक्षर वाले दो विधियां हो सकती हैं - एक वर्ग की विधि है और दूसरा स्पष्ट इंटरफ़ेस कार्यान्वयन है। कक्षा के प्रकार से निपटने के दौरान ऐसी चीज मौजूद नहीं है। मुझे लगता है कि यह यहां मुख्य अंतर है।

UPD2: अब मुझे यकीन है कि यह मामला है। वास्तविक कार्यान्वयन विस्तार के लिए this देखें।

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