2008-10-19 14 views
13

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

मूल रूप से, मैंने सोचा कि दूसरा "प्रतिनिधि" तरीका "वर्चुअल" तरीके से तेज़ होगा, लेकिन कुछ कोडिंग स्निपेट साबित करता है कि यह सही नहीं है।

नीचे कोड में, पहली DoSomething विधि "टेम्पलेट पैटर्न" का पालन करती है। यह आभासी विधि IsTokenChar पर कॉल करता है। दूसरी DoSomthing विधि वर्चुअल फ़ंक्शन पर निर्भर नहीं है। इसके बजाए, इसमें एक पास-इन प्रतिनिधि है। अपने कंप्यूटर में, पहली DoSomthing हमेशा दूसरे की तुलना में तेजी है। परिणाम 1645: 1780 की तरह है।

"आभासी आमंत्रण" गतिशील बाध्यकारी है और प्रत्यक्ष प्रतिनिधिमंडल आमंत्रण की तुलना में अधिक समय-लागत होना चाहिए, है ना? लेकिन परिणाम दिखाता है कि यह नहीं है।

किसी इस व्याख्या कर सकते हैं?

using System; 
using System.Diagnostics; 

class Foo 
{ 
    public virtual bool IsTokenChar(string word) 
    { 
     return String.IsNullOrEmpty(word); 
    } 

    // this is a template method 
    public int DoSomething(string word) 
    { 
     int trueCount = 0; 
     for (int i = 0; i < repeat; ++i) 
     { 
      if (IsTokenChar(word)) 
      { 
       ++trueCount; 
      } 
     } 
     return trueCount; 
    } 

    public int DoSomething(Predicate<string> predicator, string word) 
    { 
     int trueCount = 0; 
     for (int i = 0; i < repeat; ++i) 
     { 
      if (predicator(word)) 
      { 
       ++trueCount; 
      } 
     } 
     return trueCount; 
    } 

    private int repeat = 200000000; 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Foo f = new Foo(); 

     { 
      Stopwatch sw = Stopwatch.StartNew(); 
      f.DoSomething(null); 
      sw.Stop(); 
      Console.WriteLine(sw.ElapsedMilliseconds); 
     } 

     { 
      Stopwatch sw = Stopwatch.StartNew(); 
      f.DoSomething(str => String.IsNullOrEmpty(str), null); 
      sw.Stop(); 
      Console.WriteLine(sw.ElapsedMilliseconds); 
     } 
    } 
} 
+2

जॉन स्कीट के लिए एक, मुझे लगता है! ;) –

+0

@ मिच: मैंने वास्तव में जवाब देने से पहले आपकी टिप्पणी नहीं देखी थी, लेकिन मैं flattered हूँ :) –

+0

बीटीडब्ल्यू, मुझे लगता है कि एक अनुकूलित अनुकूलित –

उत्तर

1

यह संभव है कि जब से तुम किसी भी तरीके कि आभासी विधि है कि JIT इस समझते हैं और बजाय एक सीधा कॉल का उपयोग करने में सक्षम है ओवरराइड नहीं है।

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

+0

के साथ अधिक चिह्नित होने के लिए अंतर केवल इतना नहीं है, लेकिन अगर प्रतिनिधियों को संग्रहित/कॉल करना आभासी प्रेषण से अधिक कुशल थे, ढांचा लगभग निश्चित रूप से वर्चुअल प्रेषण का उपयोग करने के बजाय प्रतिनिधियों को स्टोर और कॉल करेगा। यह देखते हुए कि यह नहीं है, शायद यह नहीं है (दुर्लभ/रोगजनक मामलों को छोड़कर)। – technophile

+0

मुझे नहीं लगता कि यह अंतर बनाता है विधि उप-वर्ग में मूल वर्ग में "वर्चुअल" या "ओवरराइड" है। यदि यह "आभासी" है, तो यह हमेशा गतिशील बाध्यकारी होता है। –

-1

आभासी ओवरराइड में कुछ प्रकार की पुनर्निर्देशन तालिका या कुछ ऐसा है जो संकलित समय पर हार्डकोड किया गया है और पूरी तरह अनुकूलित है। यह बहुत तेजी से पत्थर में सेट है।

प्रतिनिधि गतिशील होते हैं जो हमेशा एक ओवरहेड होते हैं और वे ऑब्जेक्ट्स भी दिखते हैं ताकि वे जुड़ सकें।

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

+0

त्वरित सुधार: मुझे लगता है कि "वर्चुअल ओवरलोड" के बजाय आपको "वर्चुअल ओवरराइड" का अर्थ है –

+0

मैंने पहली बार "आभासी ओवरलैर्ड" के रूप में पढ़ा है। – Andrew

+0

हूप्स। तो मेरा इंटेलिजेंस दुर्व्यवहार दिखाता है! –

1

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

ध्यान दें कि this blog article के अनुसार अंतर .NET v1.x में भी अधिक था।

8

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

एक विधेय कॉल विधेय संपुटित को एक गुमनाम वर्ग पैदा कर रही है। उस वर्ग को तुरंत चालू किया जाना चाहिए और वास्तव में यह जांचने के लिए कुछ कोड उत्पन्न किए गए हैं कि अनुमानित फ़ंक्शन पॉइंटर शून्य है या नहीं।

मैं सुझाव दूंगा कि आप दोनों के लिए आईएल संरचना देखें। दो स्रोतों में से प्रत्येक को एक कॉल के साथ उपरोक्त स्रोत के एक सरलीकृत संस्करण को संकलित करें।फिर प्रत्येक पैटर्न के लिए वास्तविक कोड क्या है यह देखने के लिए ILDASM का उपयोग करें।

(और मुझे यकीन है कि मैं सही शब्दावली :-) का उपयोग नहीं करने के लिए downvoted कर देंगे हूँ)

+0

मेरी समझ में, "चालने की वास्तविक विधि रनटाइम पर तय की जाती है" को "गतिशील बाध्यकारी" कहा जाता है। –

+0

एक भविष्यवाणी सिर्फ एक प्रतिनिधि उदाहरण है। यह एक उदाहरण है, लेकिन मुझे यकीन नहीं है कि एक "अज्ञात वर्ग" बनाया गया है। क्या सी #/नेट में "अज्ञात वर्ग" अवधारणा है? –

+0

एक वर्चुअल कॉल "रनटाइम पर किस विधि को आमंत्रित करने का निर्णय नहीं लेता" है। आह्वान करने की विधि अच्छी तरह से ज्ञात है और कक्षा से पहले ही जुड़ी हुई है। –

19

इस स्थिति में जो आवश्यक है के बारे में सोचो:

आभासी कॉल

  • शून्यता
  • पॉइंटर
  • Loo टाइप करने के लिए ऑब्जेक्ट पॉइंटर से नेविगेट करें निर्देश तालिका में के अप विधि विधि
  • (सुनिश्चित नहीं है - यहां तक ​​कि रिचटर भी इसमें शामिल नहीं है) यदि विधि ओवरराइड नहीं है तो मूल प्रकार पर जाएं? जब तक हमें सही विधि पता नहीं मिल जाता तब तक रिकर्स करें। (मुझे ऐसा नहीं लगता है -। नीचे संपादन देखें) ढेर पर ("इस")
  • पुश मूल वस्तु सूचक
  • कॉल विधि

प्रतिनिधि कॉल

  • शून्यता
  • ऑब्जेक्ट पॉइंटर से इनवोकेशन की सरणी में नेविगेट करें (सभी प्रतिनिधि संभावित रूप से मल्टीकास्ट हैं)
  • लूप सरणी पर, और प्रत्येक मंगलाचरण के लिए:
    • लायें विधि पता
    • बाहर किया जाए या नहीं
    • पुश तर्क ढेर पर पहले तर्क के रूप में लक्ष्य को पारित करने के
    • कार्य (पहले से ही किया गया हो सकता - यकीन नहीं)
    • वैकल्पिक रूप से ढेर पर मंगलाचरण लक्ष्य धक्का (चाहे मंगलाचरण खुली या बंद है के आधार पर)
    • कॉल विधि

कुछ अनुकूलन हो सकते हैं ताकि सिंगल-कॉल केस में कोई लूपिंग शामिल न हो, लेकिन यहां तक ​​कि यह बहुत तेज़ चेक लेगा।

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

संपादित करें: मैंने "सर्वाधिक व्युत्पन्न ओवरराइडिंग" और घोषित चर प्रकार के बिंदु दोनों के साथ विरासत पदानुक्रम (20 स्तर तक) की गहराई के साथ खेलने की कोशिश की है - और उनमें से कोई भी कोई फर्क नहीं पड़ता।

संपादित करें: मैंने अभी एक इंटरफ़ेस (जो पारित किया गया है) का उपयोग करके मूल प्रोग्राम की कोशिश की है - जो प्रतिनिधि के समान प्रदर्शन के बारे में समाप्त होता है।

+0

आभासी कॉल पर शून्य के लिए कोई जांच नहीं है। इसके अलावा, विधियों का vtable संकलन समय पर निर्धारित किया जाता है, इसलिए आधार वर्गों पर कोई रन-टाइम रिकर्सन नहीं है। संकलक सूचक को उचित आधार वर्ग से सही विधि में उत्पन्न करता है और इसे vtable में उचित स्लॉट में रखता है। –

+6

कॉलवर्ट * करता है * शून्य के लिए जांचें - सीएलआई स्पेक में विभाजन 3 की धारा 4.2 देखें, या सी # के माध्यम से सीएलआर के पी 166 देखें। (यदि संदर्भ शून्य था, तो कौन सा कार्यान्वयन कहा जाएगा?) हालांकि "कोई रिकर्सन" बिट की पुष्टि के लिए धन्यवाद। यही वह प्रयोग था जो प्रयोग मूल रूप से सुझाव दे रहा था। –

+3

कोई रिकर्सन के लिए +1, vtables संकलन प्रकार पर flattened हैं। – thinkbeforecoding

12

बस कुछ सुधार जोड़ना चाहते थे जॉन स्कीट की प्रतिक्रिया करने के लिए:

एक आभासी विधि कॉल एक अशक्त चेक (स्वचालित रूप से हार्डवेयर जाल के साथ संभाला) क्या करने की जरूरत नहीं है।

गैर-ओवर्रिडेन विधियों को खोजने के लिए विरासत श्रृंखला को चलाने की आवश्यकता नहीं है (यही वर्चुअल विधि तालिका है)।

आवेदक के दौरान एक आभासी विधि कॉल अनिवार्य रूप से एक अतिरिक्त स्तर का संकेत है। टेबल लुक-अप और बाद के फ़ंक्शन पॉइंटर कॉल की वजह से यह सामान्य कॉल से धीमा है।

एक प्रतिनिधि कॉल में भी एक अतिरिक्त स्तर का संकेत शामिल है।

किसी प्रतिनिधि को कॉल करने में एक सरणी में तर्क डालने में शामिल नहीं होता है जब तक कि आप डायनामिक इनवोक विधि का उपयोग करके गतिशील चालान नहीं कर रहे हों।

एक प्रतिनिधि कॉल में कॉलिंग विधि शामिल है जिसमें एक कंपाइलर जेनरेट किया गया है, जिसमें प्रतिनिधि प्रकार पर प्रश्न पूछें। Predicator (मान) के लिए एक कॉल predicator में बदल दिया गया है। Invoke (मान)।

बदले में आमंत्रण विधि जेआईटी द्वारा फ़ंक्शन पॉइंटर (आंतरिक रूप से प्रतिनिधि ऑब्जेक्ट में संग्रहीत) को कॉल करने के लिए लागू की जाती है।

आपके उदाहरण में, आपके द्वारा पारित प्रतिनिधि को एक कंपाइलर उत्पन्न स्थैतिक विधि के रूप में कार्यान्वित किया जाना चाहिए क्योंकि कार्यान्वयन किसी भी आवृत्ति चर या स्थानीय तक नहीं पहुंचता है, इसलिए ढेर से "इस" पॉइंटर तक पहुंचने की आवश्यकता नहीं होनी चाहिए एक मुद्दा।

प्रतिनिधि और वर्चुअल फ़ंक्शन कॉल के बीच प्रदर्शन अंतर अधिकतर होना चाहिए और आपके प्रदर्शन परीक्षणों से पता चलता है कि वे बहुत करीब हैं।

अंतर मल्टीकास्ट की वजह से अतिरिक्त चेक + शाखाओं की आवश्यकता के कारण हो सकता है (जैसा कि जॉन द्वारा सुझाया गया है)। एक अन्य कारण यह हो सकता है कि जेआईटी कंपाइलर Delegate.Invoke विधि और Delegate के कार्यान्वयन को इनलाइन नहीं करता है। इन्वोक तर्क वर्चुअल विधि कॉल निष्पादित करते समय तर्कों के साथ ही कार्यान्वयन को संभाल नहीं करता है।

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