वर्चुअल फ़ंक्शंस और प्रदर्शन - C ++


125

अपने वर्ग डिजाइन में, मैं अमूर्त कक्षाओं और आभासी कार्यों का बड़े पैमाने पर उपयोग करता हूं। मुझे लग रहा था कि आभासी कार्य प्रदर्शन को प्रभावित करते हैं। क्या ये सच है? लेकिन मुझे लगता है कि यह प्रदर्शन अंतर ध्यान देने योग्य नहीं है और ऐसा लगता है कि मैं समय से पहले अनुकूलन कर रहा हूं। सही?


मेरा उत्तर के अनुसार, मैं की डुप्लीकेट के रूप में इस बंद करने की सलाह देते हैं stackoverflow.com/questions/113830
सुमा


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

जवाबों:


90

अंगूठे का एक अच्छा नियम है:

यह एक प्रदर्शन समस्या नहीं है जब तक आप इसे साबित नहीं कर सकते।

वर्चुअल फ़ंक्शंस के उपयोग से प्रदर्शन पर बहुत कम प्रभाव पड़ेगा, लेकिन यह आपके एप्लिकेशन के समग्र प्रदर्शन को प्रभावित करने की संभावना नहीं है। प्रदर्शन में सुधार के लिए बेहतर स्थान एल्गोरिदम और I / O हैं।

एक उत्कृष्ट लेख जो वर्चुअल फ़ंक्शंस (और अधिक) के बारे में बात करता है, वह है सदस्य फ़ंक्शन पॉइंटर्स और सबसे तेज़ संभावित सी ++ प्रतिनिधि


शुद्ध आभासी कार्यों के बारे में क्या? क्या वे किसी भी तरह से प्रदर्शन को प्रभावित करते हैं? बस सोच के रूप में यह लगता है कि वे वहाँ बस कार्यान्वयन को लागू करने के लिए कर रहे हैं।
थोमथोम

2
@thomthom: सही है, शुद्ध आभासी और साधारण आभासी कार्यों में कोई अंतर नहीं है।
ग्रेग हेविगिल

168

आपके प्रश्न ने मुझे जिज्ञासु बना दिया, इसलिए मैं आगे बढ़ा और 3 जीएचजेड इन-ऑर्डर पॉवरपीसी सीपीयू पर कुछ समय बिताया। मेरे द्वारा चलाया गया परीक्षण गेट / सेट फ़ंक्शंस के साथ एक साधारण 4d वेक्टर क्लास बनाना था

class TestVec 
{
    float x,y,z,w; 
public:
    float GetX() { return x; }
    float SetX(float to) { return x=to; }  // and so on for the other three 
}

फिर मैंने इनमें से प्रत्येक वैक्टर के 1024 में से प्रत्येक में तीन सरणियाँ स्थापित कीं (एल 1 में फिट करने के लिए पर्याप्त) और एक लूप चलाया जो उन्हें 1000 बार एक दूसरे (Ax = Bx + Cx) से जोड़े। मैंने इसे फ़ंक्शन के रूप inlineमें virtual, और नियमित फ़ंक्शन कॉल के साथ चलाया । यहाँ परिणाम हैं:

  • इनलाइन: 8ms (प्रति कॉल 0.65ns)
  • प्रत्यक्ष: 68ms (प्रति कॉल 5.53ns)
  • आभासी: 160ms (प्रति कॉल 13ns)

तो, इस मामले में (जहां सब कुछ कैश में फिट बैठता है) इनलाइन कॉल की तुलना में वर्चुअल फ़ंक्शन कॉल लगभग 20x धीमी थी। लेकिन इसका वास्तव में क्या मतलब है? लूप के माध्यम से प्रत्येक यात्रा बिल्कुल 3 * 4 * 1024 = 12,288फ़ंक्शन कॉल का कारण बनी (1024 वैक्टर चार घटकों को तीन कॉल प्रति बार तीन बार), इसलिए ये समय 1000 * 12,288 = 12,288,000फ़ंक्शन कॉल का प्रतिनिधित्व करते हैं। वर्चुअल लूप प्रत्यक्ष लूप की तुलना में 92ms अधिक समय लेता है, इसलिए प्रति कॉल अतिरिक्त ओवरहेड 7 नैनोसेकंड प्रति फ़ंक्शन था।

इससे मैं यह निष्कर्ष निकालता हूं: हाँ , वर्चुअल फ़ंक्शंस प्रत्यक्ष फ़ंक्शंस की तुलना में बहुत धीमी हैं, और नहीं , जब तक कि आप उन्हें प्रति सेकंड दस मिलियन बार कॉल करने की योजना नहीं बना रहे हैं, इससे कोई फर्क नहीं पड़ता।

यह भी देखें: उत्पन्न विधानसभा की तुलना।


लेकिन अगर उन्हें कई बार बुलाया जाता है, तो वे अक्सर सस्ता हो सकते हैं जब केवल एक बार बुलाया जाता है। मेरा अप्रासंगिक ब्लॉग देखें: phresnel.org/blog , "वर्चुअल फ़ंक्शंस को हानिकारक नहीं माना जाता" शीर्षक वाले पोस्ट, लेकिन निश्चित रूप से यह आपके कोडपैथ्स की जटिलता पर निर्भर करता है
सेबस्टियन मच

22
मेरा परीक्षण बार-बार कहे जाने वाले आभासी कार्यों के एक छोटे समूह को मापता है। आपका ब्लॉग पोस्ट मानता है कि कोड की समय-लागत को परिचालनों की गणना द्वारा मापा जा सकता है, लेकिन यह हमेशा सच नहीं होता है; आधुनिक प्रोसेसर पर एक vfunc की प्रमुख लागत शाखा के गलत होने के कारण पाइपलाइन बुलबुला है।
क्रैशवर्क

10
यह gcc LTO (लिंक समय अनुकूलन) के लिए एक बढ़िया बेंचमार्क होगा; lto सक्षम के साथ इसे फिर से संकलित करने का प्रयास करें: gcc.gnu.org/wiki/LinkTimeOptimization और देखें कि 20x कारक के साथ क्या होता है
lurscher

1
यदि किसी वर्ग में एक आभासी और एक इनलाइन फ़ंक्शन होता है, तो क्या गैर-आभासी पद्धति का प्रदर्शन भी प्रभावित होगा? बस वर्ग की प्रकृति आभासी होने के कारण?
थोमथोम

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

42

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


4
मुझे यकीन नहीं है कि iPhone प्रदर्शनकारी कोड का एक अच्छा उदाहरण है: youtube.com/watch?v=Pdk2cJpSXLg
Crashworks

13
@Crashworks: iPhone कोड का उदाहरण नहीं है। यह हार्डवेयर का एक उदाहरण है - विशेष रूप से धीमा हार्डवेयर , जो कि मैं यहाँ बना रहा हूं। यदि ये प्रतिष्ठित "धीमी" भाषाएं कमज़ोर हार्डवेयर के लिए पर्याप्त हैं, तो वर्चुअल फ़ंक्शंस बहुत बड़ी समस्या नहीं होगी।
चक

52
IPhone एआरएम प्रोसेसर पर चलता है। IOS के लिए उपयोग किए गए ARM प्रोसेसर कम MHz और कम बिजली के उपयोग के लिए डिज़ाइन किए गए हैं। सीपीयू पर शाखा भविष्यवाणी के लिए कोई सिलिकॉन नहीं है और इसलिए वर्चुअल फ़ंक्शन कॉल से शाखा पूर्वानुमान से कोई प्रदर्शन ओवरहेड नहीं होता है। इसके अलावा iOS हार्डवेयर के लिए मेगाहर्ट्ज काफी कम है कि एक कैश मिस 300 घड़ी चक्र के लिए प्रोसेसर को स्टाल नहीं करता है जबकि यह रैम से डेटा पुनर्प्राप्त करता है। कम मेगाहर्ट्ज में कैश मिक्स कम महत्वपूर्ण हैं। संक्षेप में, iOS उपकरणों पर वर्चुअल फ़ंक्शंस का उपयोग करने से कोई ओवरहेड नहीं होता है, लेकिन यह एक हार्डवेयर समस्या है और सीपीयू डेस्कटॉप पर लागू नहीं होती है।
HaltingState

4
लंबे समय तक जावा प्रोग्रामर सी ++ में नया रहने के बाद, मैं यह जोड़ना चाहता हूं कि जावा के जेआईटी कंपाइलर और रन-टाइम ऑप्टिमाइज़र में लूप की पूर्वनिर्धारित संख्या के बाद रन-टाइम पर कुछ कार्यों को संकलित करने, भविष्यवाणी करने और यहां तक ​​कि इनलाइन करने की क्षमता है। हालाँकि मुझे यकीन नहीं है कि C ++ में संकलन और लिंक समय पर ऐसी सुविधा है क्योंकि इसमें रनटाइम कॉल पैटर्न का अभाव है। इस प्रकार C ++ में हमें थोड़ा अधिक सावधान रहने की आवश्यकता हो सकती है।
एलेक्स सू

@AlexSuo मुझे आपकी बात का यकीन नहीं है? संकलित होने के कारण, C ++ निश्चित रूप से रनटाइम के दौरान क्या हो सकता है, के आधार पर अनुकूलन नहीं कर सकता है, इसलिए भविष्यवाणी आदि सीपीयू द्वारा ही की जानी होगी ... लेकिन अच्छा C ++ कंपाइलर (यदि निर्देश दिया गया है) फ़ंक्शन का अनुकूलन करने के लिए बड़ी लंबाई में जाता है और बहुत पहले ही लूप हो जाता है क्रम।
अंडरस्कोर_ड

34

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

एक सामान्य फ़ंक्शन कॉल एक निर्देश कैश मिस उत्पन्न कर सकता है जब CPU नए फ़ंक्शन का पहला निर्देश प्राप्त करता है और यह कैश में नहीं है।

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

कई मामलों में, दो अतिरिक्त कैश मिस एक चिंता का विषय नहीं हैं, लेकिन प्रदर्शन महत्वपूर्ण कोड पर एक तंग पाश में यह नाटकीय रूप से प्रदर्शन को कम कर सकता है।


6
ठीक है, लेकिन किसी भी कोड (या वीभत्स) को एक तंग लूप से बार-बार कहा जाता है (निश्चित रूप से) शायद ही कभी कैश की कमी से ग्रस्त है। इसके अलावा, वॉयटेबल पॉइंटर आमतौर पर उसी कैशे लाइन में होता है जो ऑब्जेक्ट में अन्य डेटा के रूप में होता है जिसे तथाकथित विधि एक्सेस करेगी, इसलिए अक्सर हम केवल एक अतिरिक्त कैश मिस के बारे में बात कर रहे हैं।
क्वर्टी

5
@ क्वर्टी मुझे नहीं लगता कि यह आवश्यक है। लूप का शरीर (यदि L1 कैशे से बड़ा है) vtable पॉइंटर, फंक्शन पॉइंटर और बाद में आने वाले रिटायरमेंट को रिटायर कर सकता है, तो इसे हर पुनरावृत्ति पर L2 कैश (या अधिक) एक्सेस के लिए इंतजार करना होगा
Ghita

30

Agner Fog के पेज 44 से "C ++ में ऑप्टिमाइज़िंग सॉफ्टवेयर" मैनुअल :

वर्चुअल मेम्बर फ़ंक्शन को कॉल करने में लगने वाला समय कुछ क्लॉक साइकल है जितना कि किसी नॉन-वर्चुअल मेंबर फंक्शन को कॉल करने में अधिक समय लगता है, बशर्ते कि फंक्शन कॉल स्टेटमेंट हमेशा वर्चुअल फंक्शन के एक ही वर्जन को कॉल करता हो। यदि संस्करण बदल जाता है तो आपको 10 - 30 घड़ी चक्र का गलत दंड मिलेगा। वर्चुअल फ़ंक्शन कॉल की भविष्यवाणी और गलतफहमी के नियम स्विच स्टेटमेंट के लिए समान हैं ...


इस संदर्भ के लिए धन्यवाद। Agner Fog का ऑप्टिमाइज़ेशन मैनुअल हार्डवेयर के बेहतर उपयोग के लिए सोने का मानक है।
आर्टो बेंडिकेन

मेरे स्मरण और एक त्वरित खोज के आधार पर - stackoverflow.com/questions/17061967/c-switch-and-jump-tables - मुझे संदेह है कि यह हमेशा के लिए सच है switch। पूरी तरह से मनमाने caseमूल्यों के साथ, सुनिश्चित करें। लेकिन अगर सभी caseलगातार होते हैं, तो एक कंपाइलर इसे जंप-टेबल (आह, जो मुझे अच्छे पुराने Z80 दिनों की याद दिलाता है) में अनुकूलित करने में सक्षम हो सकता है, जो निरंतर समय के लिए (एक बेहतर अवधि के लिए) होना चाहिए। ऐसा नहीं है कि मैं vfuncs को बदलने की कोशिश कर रहा हूं switch, जो कि आकर्षक है। ;)
अंडरस्कोर_ड

7

पूर्ण रूप से। जब कंप्यूटर 100Mhz पर चलता था, तो यह एक समस्या थी, क्योंकि हर विधि कॉल को कॉल करने से पहले vtable पर लुकअप की आवश्यकता होती थी। लेकिन आज .. एक 3Ghz CPU पर जो मेरे प्रथम कंप्यूटर की तुलना में अधिक मेमोरी वाला 1st स्तर कैश है? हर्गिज नहीं। यदि आपके सभी कार्य वर्चुअल थे, तो मुख्य रैम से मेमोरी आवंटित करने में आपको अधिक समय लगेगा।

इसके पुराने, पुराने दिनों की तरह, जहां लोगों ने कहा कि संरचित प्रोग्रामिंग धीमी थी क्योंकि सभी कोड कार्यों में विभाजित थे, प्रत्येक फ़ंक्शन को स्टैक आवंटन और फ़ंक्शन कॉल की आवश्यकता थी!

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

PS अन्य 'आसान भाषा' का उपयोग करने के बारे में सोचते हैं - उनके सभी तरीके कवर के तहत आभासी हैं और वे आजकल क्रॉल नहीं करते हैं।


4
खैर, आज भी फंक्शन कॉल्स से बचना हाई-परफैक्ट ऐप्स के लिए महत्वपूर्ण है। अंतर यह है, आज के कंपाइलर्स छोटे कार्यों को मज़बूती से इनलाइन करते हैं ताकि हम छोटे कार्यों को लिखने के लिए गति का दंड न भुगतें। आभासी कार्यों के लिए, स्मार्ट सीपीयू उन पर स्मार्ट शाखा की भविष्यवाणी कर सकते हैं। तथ्य यह है कि पुराने कंप्यूटर धीमे थे, मुझे लगता है, वास्तव में मुद्दा नहीं है - हाँ, वे बहुत धीमे थे, लेकिन फिर हम जानते थे कि, इसलिए हमने उन्हें बहुत छोटे वर्कलोड दिए। 1992 में यदि हम एक एमपी 3 बजाते थे, तो हमें पता था कि हमें सीपीयू के आधे से अधिक भाग को उस कार्य के लिए समर्पित करना पड़ सकता है।
19 अक्टूबर को क्वर्टी

6
एमपी में 1995 से तारीखें। 92 में हमारे पास मुश्किल से 386 थे, किसी भी तरह से वे एमपी 3 नहीं बजा सकते थे, और 50% सीपीयू समय एक अच्छा मल्टी टास्क ओएस, एक बेकार प्रक्रिया और एक प्रीमेप्टिव शेड्यूलर मानता है। इस समय उपभोक्ता बाजार में कोई भी मौजूद नहीं था। यह क्षण शक्ति से 100% था, कहानी का अंत था।
v.oddou

7

निष्पादन समय के अलावा एक और प्रदर्शन मानदंड है। एक Vtable मेमोरी स्पेस भी लेता है, और कुछ मामलों में बचा जा सकता है: ATL संकलन-टाइम " सिम्युलेटेड डायनामिक बाइंडिंग " टेम्प्लेट के साथ उपयोग करता है"स्थिर बहुरूपता" का प्रभाव प्राप्त करने के लिए, जो समझाने में कठिन है; आप मूल रूप से बेस क्लास टेम्पलेट के पैरामीटर के रूप में व्युत्पन्न वर्ग को पास करते हैं, इसलिए संकलन समय पर बेस क्लास "जानता है" कि प्रत्येक व्युत्पन्न वर्ग प्रत्येक उदाहरण में क्या है। आपको आधार प्रकारों के संग्रह में कई अलग-अलग व्युत्पन्न कक्षाएं संग्रहीत करने की अनुमति नहीं देगा (जो कि रन-टाइम बहुरूपता है) लेकिन एक स्थिर अर्थ से, यदि आप एक वर्ग वाई बनाना चाहते हैं जो कि एक preexisting टेम्पलेट कक्षा X के समान है इस तरह के ओवरराइडिंग के लिए हुक, आपको बस उन तरीकों को ओवरराइड करने की ज़रूरत है जिनकी आप परवाह करते हैं, और फिर आपको एक vable होने के बिना दसवीं कक्षा के आधार तरीके मिलते हैं।

बड़ी मेमोरी वाले पैरों के निशान वाली कक्षाओं में, एक एकल व्यवहार्य पॉइंटर की लागत बहुत अधिक नहीं होती है, लेकिन COM में एटीएल की कुछ कक्षाएं बहुत छोटी होती हैं, और यह व्यवहार्य बचत के लायक है यदि रन-टाइम पॉलीमॉर्फिज्म का मामला कभी नहीं होने वाला है।

यह अन्य SO प्रश्न भी देखें

यहाँ एक पोस्टिंग से मैंने पाया कि सीपीयू-टाइम प्रदर्शन पहलुओं के बारे में बात करता है।



4

हां, आप सही हैं और यदि आप वर्चुअल फ़ंक्शन कॉल की लागत के बारे में उत्सुक हैं तो आपको यह पोस्ट दिलचस्प लग सकती है ।


1
लिंक किए गए लेख वर्चुअल कॉल का बहुत महत्वपूर्ण हिस्सा नहीं मानते हैं, और यह संभव है कि शाखा की गलतफहमी हो।
सूमा

4

एकमात्र तरीका जो मैं देख सकता हूं कि एक वर्चुअल फ़ंक्शन एक प्रदर्शन समस्या बन जाएगा यदि कई वर्चुअल फ़ंक्शंस एक तंग लूप के भीतर बुलाए जाते हैं, और यदि और केवल अगर वे एक पेज गलती या अन्य "भारी" मेमोरी ऑपरेशन होने का कारण बनते हैं।

हालांकि अन्य लोगों की तरह यह बहुत ज्यादा वास्तविक जीवन में आप के लिए एक समस्या होने जा रहा है कभी नहीं कहा है। और अगर आपको लगता है कि यह है, तो एक प्रॉफ़ाइलर चलाएं, कुछ परीक्षण करें, और सत्यापित करें कि क्या प्रदर्शन के लाभ के लिए अपने कोड को "अनदेखा" करने की कोशिश करने से पहले यह वास्तव में एक समस्या है।


2
एक तंग लूप में कुछ भी कॉल करने से उस सभी कोड और डेटा को कैश में गर्म रखने की संभावना है ...
ग्रेग रोजर्स

2
हां, लेकिन अगर वह दायां लूप ऑब्जेक्ट्स की सूची के माध्यम से पुनरावृत्त हो रहा है, तो प्रत्येक ऑब्जेक्ट संभवतः एक ही फ़ंक्शन कॉल के माध्यम से एक अलग पते पर वर्चुअल फ़ंक्शन को कॉल कर सकता है।
Daemin

3

जब क्लास विधि वर्चुअल नहीं होती है, तो कंपाइलर आमतौर पर इन-लाइनिंग करता है। इसके विपरीत, जब आप वर्चुअल फ़ंक्शन के साथ कुछ कक्षा के लिए पॉइंटर का उपयोग करते हैं, तो वास्तविक पता केवल रनटाइम पर जाना जाएगा।

यह अच्छी तरह से परीक्षण, समय अंतर ~ 700% (!) द्वारा सचित्र है:

#include <time.h>

class Direct
{
public:
    int Perform(int &ia) { return ++ia; }
};

class AbstrBase
{
public:
    virtual int Perform(int &ia)=0;
};

class Derived: public AbstrBase
{
public:
    virtual int Perform(int &ia) { return ++ia; }
};


int main(int argc, char* argv[])
{
    Direct *pdir, dir;
    pdir = &dir;

    int ia=0;
    double start = clock();
    while( pdir->Perform(ia) );
    double end = clock();
    printf( "Direct %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    Derived drv;
    AbstrBase *ab = &drv;

    ia=0;
    start = clock();
    while( ab->Perform(ia) );
    end = clock();
    printf( "Virtual: %.3f, ia=%d\n", (end-start)/CLOCKS_PER_SEC, ia );

    return 0;
}

आभासी फ़ंक्शन कॉल का प्रभाव स्थिति पर निर्भर करता है। यदि फ़ंक्शन के अंदर कुछ कॉल और महत्वपूर्ण मात्रा में कार्य होते हैं - तो यह नगण्य हो सकता है।

या, जब यह एक वर्चुअल कॉल है जिसे कई बार इस्तेमाल किया जाता है, जबकि कुछ सरल ऑपरेशन करते हैं - यह वास्तव में बड़ा हो सकता है।


4
की तुलना में एक आभासी फ़ंक्शन कॉल महंगा है ++ia। तो क्या?
बो पर्सन

2

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

क्या प्रदर्शन एक आधुनिक लैपटॉप / डेस्कटॉप / टैबलेट पर ध्यान देने योग्य है। शायद नहीं! हालांकि, एम्बेडेड सिस्टम के साथ कुछ मामलों में, प्रदर्शन हिट आपके कोड की अक्षमता में ड्राइविंग कारक हो सकता है, खासकर अगर वर्चुअल फ़ंक्शन को बार-बार लूप में कहा जाता है।

यहाँ कुछ-कुछ दिनांकित पेपर है जो एम्बेडेड सिस्टम संदर्भ में C / C ++ के लिए सर्वोत्तम अभ्यासों को प्रस्तुत करता है: http://www.open-std.org/jtc1/sc22/wg21/docs/ESC_Boston_01-304_paper.pdf

समाप्त करने के लिए: यह प्रोग्रामर के ऊपर निर्भर है कि वह एक दूसरे पर एक निश्चित निर्माण का उपयोग करने के पेशेवरों / विपक्षों को समझ सके। जब तक आप सुपर परफॉरमेंस से प्रेरित नहीं होते हैं, आप शायद प्रदर्शन हिट की परवाह नहीं करते हैं और अपने कोड को यथासंभव उपयोगी बनाने के लिए C ++ में सभी साफ़ OO सामान का उपयोग करना चाहिए।


2

मेरे अनुभव में, मुख्य प्रासंगिक बात एक फ़ंक्शन को इनलाइन करने की क्षमता है। यदि आपके पास प्रदर्शन / अनुकूलन की आवश्यकता है जो एक फ़ंक्शन को निर्धारित करता है, तो आपको फ़ंक्शन को वर्चुअल करना होगा, क्योंकि यह रोक नहीं सकता है। अन्यथा, आप शायद अंतर नहीं देखेंगे।


1

एक बात ध्यान देने योग्य है कि यह:

boolean contains(A element) {
    for (A current: this)
        if (element.equals(current))
            return true;
    return false;
}

इससे तेज हो सकता है:

boolean contains(A element) {
    for (A current: this)
        if (current.equals(equals))
            return true;
    return false;
}

ऐसा इसलिए है क्योंकि पहला तरीका केवल एक फ़ंक्शन को कॉल कर रहा है, जबकि दूसरा कई अलग-अलग फ़ंक्शन को कॉल कर सकता है। यह किसी भी भाषा में किसी भी आभासी कार्य पर लागू होता है।

मैं कहता हूं "हो सकता है" क्योंकि यह संकलक, कैश आदि पर निर्भर करता है।


0

वर्चुअल फ़ंक्शंस का उपयोग करने का प्रदर्शन दंड कभी भी डिज़ाइन स्तर पर आपको मिलने वाले फायदों को कम नहीं कर सकता है। माना जाता है कि किसी वर्चुअल फंक्शन में कॉल 25% कम कुशल होगी, फिर स्टैटिक फंक्शन के लिए डायरेक्ट कॉल। ऐसा इसलिए है क्योंकि VMT के माध्यम से अप्रत्यक्ष का एक स्तर है। हालाँकि कॉल करने के लिए लिया गया समय आपके फ़ंक्शन के वास्तविक निष्पादन में लगने वाले समय की तुलना में सामान्य रूप से बहुत कम है, इसलिए कुल प्रदर्शन लागत उदासीन होगी, विशेष रूप से हार्डवेयर के वर्तमान प्रदर्शन के साथ। इसके अलावा कंपाइलर कभी-कभी अनुकूलन कर सकता है और देख सकता है कि किसी वर्चुअल कॉल की जरूरत नहीं है और इसे स्टैटिक कॉल में संकलित करें। तो चिंता मत करो आभासी कार्यों और अमूर्त वर्गों के रूप में ज्यादा के रूप में आप की जरूरत है।


2
कभी नहीं, कोई फर्क नहीं पड़ता कि लक्ष्य कंप्यूटर कितना छोटा है?
जुमलिफगार्ड

मैं सहमत हो सकता है कि आपने यह घोषणा की थी कि जैसा कि The performance penalty of using virtual functions can sometimes be so insignificant that it is completely outweighed by the advantages you get at the design level.महत्वपूर्ण अंतर कह रहा है sometimes, नहीं never
अंडरस्कोर_ड

-1

मैंने हमेशा अपने आप से यह सवाल किया, खासकर जब से - कुछ साल पहले - मैंने भी ऐसा परीक्षण किया था जिसमें एक मानक सदस्य विधि कॉल के समय की तुलना एक आभासी एक के साथ की गई थी और वास्तव में उस समय के परिणामों के बारे में गुस्सा था, खाली आभासी कॉल होने के कारण गैर-आभासी की तुलना में 8 गुना धीमा।

आज मुझे यह तय करना था कि मैं अपने बफर क्लास में अधिक मेमोरी आवंटित करने के लिए वर्चुअल फ़ंक्शन का उपयोग करूं या नहीं, बहुत ही महत्वपूर्ण आलोचनात्मक ऐप में, इसलिए मैंने googled (और आपको पाया), और अंत में, फिर से परीक्षण किया।

// g++ -std=c++0x -o perf perf.cpp -lrt
#include <typeinfo>    // typeid
#include <cstdio>      // printf
#include <cstdlib>     // atoll
#include <ctime>       // clock_gettime

struct Virtual { virtual int call() { return 42; } }; 
struct Inline { inline int call() { return 42; } }; 
struct Normal { int call(); };
int Normal::call() { return 42; }

template<typename T>
void test(unsigned long long count) {
    std::printf("Timing function calls of '%s' %llu times ...\n", typeid(T).name(), count);

    timespec t0, t1;
    clock_gettime(CLOCK_REALTIME, &t0);

    T test;
    while (count--) test.call();

    clock_gettime(CLOCK_REALTIME, &t1);
    t1.tv_sec -= t0.tv_sec;
    t1.tv_nsec = t1.tv_nsec > t0.tv_nsec
        ? t1.tv_nsec - t0.tv_nsec
        : 1000000000lu - t0.tv_nsec;

    std::printf(" -- result: %d sec %ld nsec\n", t1.tv_sec, t1.tv_nsec);
}

template<typename T, typename Ua, typename... Un>
void test(unsigned long long count) {
    test<T>(count);
    test<Ua, Un...>(count);
}

int main(int argc, const char* argv[]) {
    test<Inline, Normal, Virtual>(argc == 2 ? atoll(argv[1]) : 10000000000llu);
    return 0;
}

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


12
मुझे लगता है कि यह संभावना है कि आपका संकलक बता सकता है कि आपके कोड में वर्चुअल फ़ंक्शन कॉल केवल वर्चुअल :: कॉल कर सकता है। उस मामले में यह सिर्फ इनलाइन कर सकता है। कंपाइलर को इनलाइनिंग से रोकने के लिए कुछ भी नहीं है सामान्य :: कॉल भले ही आपने इसे नहीं पूछा। इसलिए मुझे लगता है कि यह बहुत संभव है कि आप 3 ऑपरेशन के लिए एक ही समय प्राप्त करें क्योंकि कंपाइलर उनके लिए समान कोड उत्पन्न कर रहा है।
बजरके एच। रौने जूल 6'11
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.