2009-03-24 18 views
8

में वर्चुअल कॉल की गति मुझे लगता है कि सी # में वर्चुअल कॉल की लागत उतनी अधिक नहीं है जितनी सी ++ में अपेक्षाकृत बोल रही है। क्या ये सच है? यदि हां - क्यों?सी # बनाम सी ++

उत्तर

8

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

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

यह भी बहुत संभावना है कि यदि कॉल लूप में कॉल किया जाता है तो सभी प्रोसेसर निर्देश पहले से ही स्तर 1 कैश में होंगे। लेकिन डेटा कैश होने की संभावना कम है, इन दिनों मुख्य मेमोरी से डेटा वैल्यू पढ़ने की लागत स्तर 1 कैश से 100 के निर्देशों को चलाने के समान है। इसलिए यह दुर्भाग्यपूर्ण है कि वास्तविक अनुप्रयोगों में वर्चुअल कॉल की लागत बहुत कम जगहों पर भी मापनीय है।

तथ्य यह है कि सी # कोड कुछ और निर्देशों का उपयोग करता है, निश्चित रूप से कैश में फिट होने वाले कोड की मात्रा को कम कर देगा, इसका प्रभाव भविष्यवाणी करना असंभव है।

(सी ++ वर्ग कई जन्मजात होना उपयोग करती है तो लागत अधिक होने के कारण "इस" सूचक ऊपर पैच करने के लिए हो रही है। इसी तरह में इंटरफेस सी # पुनर्निर्देशन का एक और स्तर जोड़ सकते हैं।)

2

सी ++ में वर्चुअल कॉल की लागत एक पॉइंटर (vtbl) के माध्यम से फ़ंक्शन कॉल का है। मुझे शक है कि सी # एक तेजी से और अभी भी कार्यावधि में ऑब्जेक्ट प्रकार निर्धारित करने के लिए सक्षम किया जा रहा ऐसा कर सकते हैं ...

संपादित करें: के रूप में पीट Kirkham ने कहा, एक अच्छा JIT, सी # कॉल इनलाइन करने के लिए सक्षम हो सकता है एक से परहेज पाइपलाइन स्टॉल; कुछ सी ++ कंपाइलर कुछ नहीं कर सकते (अभी तक)। दूसरी ओर, इयान रिंगरोस ने कैश के उपयोग पर प्रभाव का उल्लेख किया। उसमें जोड़कर, जेआईटी स्वयं चल रहा है, और (सख्ती से व्यक्तिगत रूप से) मैं वास्तव में परेशान नहीं करता जब तक कि यथार्थवादी वर्कलोड के तहत लक्ष्य मशीन पर प्रोफाइलिंग सिद्ध एक दूसरे की तुलना में तेज़ हो। यह सबसे अच्छा माइक्रो-अनुकूलन है।

3

मुझे लगता है कि यह धारणा जेआईटी-कंपाइलर पर आधारित है, जिसका अर्थ है कि सी # संभवतया एक वर्चुअल कॉल को एक साधारण विधि कॉल में परिवर्तित करता है, जिसका उपयोग वास्तव में किया जाता है।

लेकिन यह अनिवार्य रूप से सैद्धांतिक है और मैं इस पर शर्त नहीं लगाऊंगा!

+0

भले ही यह मामला हो; कि "एक साधारण विधि कॉल में परिवर्तित करना" मुफ्त में नहीं है, अब यह है? – DevSolar

+0

नहीं, बिल्कुल नहीं। लेकिन वास्तविक कॉल करने के दौरान भुगतान करने के लिए आपके पास कुछ भी नहीं बचा होगा (जैसे अग्रिम भुगतान करना)। –

+0

प्वाइंट है, अगली बार जब आप वह कॉल करेंगे, तो आपको ऑब्जेक्ट को दोबारा जांचना होगा। आम तौर पर, यह बदल सकता है, इसलिए obj.foo() प्रत्येक बार एक अलग foo को संदर्भित करता है। ध्यान दें कि यदि ऑब्जेक्ट प्रकार संकलन समय पर ज्ञात है तो C++ कंपाइलर्स अक्सर वर्चुअल कॉल को सामान्य कॉल में भी परिवर्तित कर सकते हैं। – MSalters

1

पूर्ण ढांचे के बारे में निश्चित नहीं है लेकिन कॉम्पैक्ट फ्रेमवर्क में यह धीमा कारण होगा क्योंकि सीएफ में कोई वर्चुअल कॉल टेबल नहीं है, हालांकि यह परिणाम कैश करता है। इसका मतलब यह है कि सीएफ में वर्चुअल कॉल पहली बार धीमा हो जाएगा क्योंकि इसे मैन्युअल लुकअप करना है। जब भी ऐप मेमोरी पर कम होता है तो यह धीमा हो सकता है क्योंकि कैश किए गए लुकअप को दबाया जा सकता है।

5

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

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

सीमित मामले में, जहां वर्चुअल कॉल का केवल एक ही कार्यान्वयन है और कॉल का मुख्य भाग काफी छोटा है, वर्चुअल कॉल पूरी तरह से inline code तक कम हो गया है। इस तकनीक का उपयोग Self language रनटाइम में किया गया था, जिसे जेवीएम से विकसित किया गया था।

अधिकांश सी ++ कंपाइलर इस अनुकूलन को करने के लिए आवश्यक संपूर्ण प्रोग्राम विश्लेषण नहीं करते हैं, लेकिन एलएलवीएम जैसी परियोजनाएं इस तरह के पूरे प्रोग्राम अनुकूलन को देख रही हैं।

+0

क्या आप वाकई एक पाइपलाइन स्टॉल का कारण बनते हैं? ऐसा कोई कारण नहीं है कि कोई CPU अप्रत्यक्ष कॉल को प्रीफ़ेच नहीं कर सकता (और मुझे लगता है कि यह इंटेल विशेष रूप से लक्षित करेगा ...)। यदि इसे प्रीफेच किया जा सकता है तो वर्चुअल फ़ंक्शन का ओवरहेड शून्य होगा। –

+0

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

+0

मुझे लगता है कि दूसरी बार एक ही अप्रत्यक्ष कॉल उसी कॉल साइट से लिया जाता है, वहां कोई स्टॉल नहीं होगा। उदा। यदि एक लूप एक ही प्रकार के कई ऑब्जेक्ट पर वर्चुअल कॉल मैक कर रहा है तो यह ठीक रहेगा। –

0

सी # में यह हो सकता है कोड का विश्लेषण करके वर्चुअल फ़ंक्शन को गैर-आभासी में परिवर्तित करना संभव है। अभ्यास में यह अक्सर अधिक अंतर करने के लिए पर्याप्त नहीं होगा।

0

सी # vtable flattens और पूर्वजों की कॉल इनलाइन करता है ताकि आप कुछ भी हल करने के लिए विरासत पदानुक्रम की श्रृंखला नहीं बनाते हैं।

0

यह आपके प्रश्न का बिल्कुल सही जवाब नहीं हो सकता है, लेकिन हालांकि .NET JIT वर्चुअल कॉल को अनुकूलित करता है क्योंकि सभी ने पहले कहा था, profile-guided optimization विजुअल स्टूडियो 2005 और 2008 में वर्चुअल कॉल अटकलें सबसे अधिक लक्षित लक्षित कॉल को प्रत्यक्ष कॉल करके समारोह, कॉल इनलाइन, तो वजन वही हो सकता है।

4

मूल प्रश्न का कहना है:

मैंने कहीं पढ़ने कि सी # में एक आभासी कॉल की लागत के रूप में उच्च के रूप में सी में अपेक्षाकृत बोल,, नहीं है ++ याद करने लगते हैं।

जोर दें। दूसरे शब्दों में, सवाल rephrased जा सकता है के रूप में:

मैंने कहीं पढ़ने कि सी #, में आभासी और गैर आभासी कॉल समान रूप से धीमी गति से कर रहे हैं याद करने लगते हैं, जबकि C++ एक आभासी कॉल एक की तुलना में धीमी गैर वर्चुअल कॉल ...

तो प्रश्नकर्ता यह दावा नहीं कर रहा है कि सी # किसी भी परिस्थिति में सी ++ से तेज है।

संभवतः एक बेकार मोड़, लेकिन इसने सी ++ से संबंधित/जिज्ञासा से शुद्ध जिज्ञासा बढ़ा दी: शुद्ध, कोई सी ++/सीएलआई एक्सटेंशन का उपयोग नहीं किया। कंपाइलर आईएल उत्पन्न करता है जो जेआईटी द्वारा मूल कोड में परिवर्तित हो जाता है, हालांकि यह शुद्ध सी ++ है। तो यहां हमारे पास यह देखने का एक तरीका है कि सी # के समान मंच पर चलने पर मानक सी ++ कार्यान्वयन क्या करता है।

struct Plain 
{ 
    void Bar() { System::Console::WriteLine("hi"); } 
}; 

इस कोड:

Plain *p = new Plain(); 
p->Bar(); 

..., का कारण बनता है call opcode विशिष्ट विधि नाम के साथ उत्सर्जित होना बार गुजर एक अंतर्निहित this तर्क

एक गैर आभासी विधि के साथ

struct Base 
{ 
    virtual void Bar() = 0; 
}; 

struct Derived : Base 
{ 
    void Bar() { System::Console::WriteLine("hi"); } 
}; 

अब अगर हम करते हैं:

call void <Module>::Plain.Bar(valuetype Plain*) 

एक वंशानुगत पदानुक्रम के साथ तुलना करें

Base *b = new Derived(); 
b->Bar(); 

बजाय calli opcode का उत्सर्जन करता है यही कारण है, जो एक गणना का पता करने के लिए कूदता है - तो वहाँ एक बहुत कुछ है कॉल से पहले आईएल का।

**(*((int*) b))(b); 

दूसरे शब्दों में (जो एक सूचक के रूप में एक ही आकार के होने के लिए होता है), एक सूचक को b का पता डाली int करने के लिए और ले: सी # करने के लिए इसे वापस चालू करने से हम क्या चल रहा है देख सकते हैं उस स्थान पर मूल्य, जो vtable का पता है, और उसके बाद vtable में पहला आइटम लेता है, जो कि कूदने के लिए पता है, इसे अस्वीकार करता है और इसे कॉल करता है, इसे अंतर्निहित this तर्क पास करता है। के रूप में यह सी # में होगा बिल्कुल

ref struct Base 
{ 
    virtual void Bar() = 0; 
}; 

ref struct Derived : Base 
{ 
    virtual void Bar() override { System::Console::WriteLine("hi"); } 
}; 

Base ^b = gcnew Derived(); 
b->Bar(); 

यह callvirt opcode उत्पन्न करता है,:

callvirt instance void Base::Bar() 

तो जब लक्षित करने के लिए संकलन

हम C++/CLI एक्सटेंशन का उपयोग करने आभासी उदाहरण ठीक कर सकते हैं सीएलआर, माइक्रोसॉफ्ट के वर्तमान सी ++ कंपाइलर में अनुकूलन के लिए समान संभावनाएं नहीं हैं क्योंकि प्रत्येक भाषा की मानक विशेषताओं का उपयोग करते समय सी # करता है; एक मानक सी ++ वर्ग पदानुक्रम के लिए, सी ++ कंपाइलर कोड उत्पन्न करता है जिसमें vtable को घुमाने के लिए हार्ड-कोडेड तर्क शामिल होता है, जबकि एक रेफ क्लास के लिए यह इष्टतम कार्यान्वयन को समझने के लिए इसे JIT पर छोड़ देता है।

+0

यह सीएलआर के शीर्ष पर सी ++ के बारे में है, जो वास्तव में उचित खेल IMHO नहीं है। – DevSolar

+0

इसके अलावा, इस संदर्भ में "निष्पक्ष" का क्या अर्थ है? –

+0

जोहान ने सी ++ की तुलना में सी # में "सस्ता" होने पर आभासी कॉल के बारे में पूछा। मैं "सी ++" का अर्थ "सी ++ मूल के लिए संकलित" लेता हूं। तो एमएससी ++ आईएल "कॉलवर्ट" में एक सी ++ आभासी कॉल संकलित नहीं कर सकता है। "क्यों नहीं?" एमएस कंपाइलर के लिए लक्ष्य होना चाहिए, भाषा नहीं, है ना? – DevSolar

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