C ++ क्लास में वर्चुअल मेथड होने की परफॉर्मेंस कॉस्ट क्या है?


107

C ++ क्लास (या उसके किसी भी पैरेंट क्लास) में कम से कम एक वर्चुअल तरीका होने का मतलब है कि क्लास में वर्चुअल टेबल होगी और हर इंस्टेंस में वर्चुअल पॉइंटर होगा।

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

यह मुझे मेरे सवाल पर लाता है: क्या विधि को आभासी बनाने के लिए औसत दर्जे का प्रदर्शन लागत (यानी गति प्रभाव) है? प्रत्येक विधि कॉल पर रनटाइम पर वर्चुअल टेबल में एक लुकअप होगा, इसलिए यदि इस पद्धति पर बहुत बार कॉल होते हैं, और यदि यह विधि बहुत कम है, तो एक औसत दर्जे का प्रदर्शन हिट हो सकता है? मुझे लगता है कि यह प्लेटफ़ॉर्म पर निर्भर करता है, लेकिन क्या किसी ने कुछ बेंचमार्क चलाए हैं?

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



7
वर्चुअल को नॉन वर्चुअल कॉल की तुलना करना मेनिंगफुल नहीं है। वे विभिन्न कार्यक्षमता प्रदान करते हैं। यदि आप C के समतुल्य वर्चुअल फ़ंक्शन कॉल की तुलना करना चाहते हैं, तो आपको उस कोड की लागत को जोड़ना होगा जो वर्चुअल फ़ंक्शन के समकक्ष फ़ीचर को लागू करता है।
मार्टिन यॉर्क

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


7
सवाल फंक्शन कॉल के बारे में है जो आभासी होने की आवश्यकता नहीं है, इसलिए तुलना सार्थक है।
मार्क रैनसम

जवाबों:


104

मैं एक 3ghz इन-ऑर्डर PowerPC प्रोसेसर पर कुछ समय चला । उस आर्किटेक्चर पर, एक वर्चुअल फंक्शन कॉल की लागत 7 नैनोसेकंड्स की तुलना में प्रत्यक्ष (गैर-वर्चुअल) फ़ंक्शन कॉल से अधिक है।

इसलिए, वास्तव में लागत के बारे में चिंता करने योग्य नहीं है जब तक कि फ़ंक्शन एक तुच्छ हो जाओ () / सेट () एक्सेसर की तरह कुछ नहीं है, जिसमें इनलाइन के अलावा कुछ भी बेकार की तरह है। एक समारोह पर 7ns ओवरहेड जो 0.5ns के लिए गंभीर है; एक फ़ंक्शन पर 7ns ओवरहेड जो निष्पादित करने के लिए 500ms लेता है, अर्थहीन है।

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

संपादित करें: एंड्रयू, नॉट श्योर, और अन्य लोग भी बहुत अच्छा मुद्दा उठाते हैं कि वर्चुअल फ़ंक्शन कॉल से एक निर्देश कैश मिस हो सकता है: यदि आप एक कोड पते पर कूदते हैं जो कैश में नहीं है, तो पूरा कार्यक्रम मृत हो जाता है जबकि निर्देश मुख्य मेमोरी से प्राप्त होते हैं। यह हमेशा एक महत्वपूर्ण स्टाल है: क्सीनन पर, लगभग 650 चक्र (मेरे परीक्षणों द्वारा)।

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

Icache के प्रभाव के लिए मेरा समय नियंत्रण निष्पादन पर याद करता है (जानबूझकर, चूंकि मैं अलगाव में सीपीयू पाइपलाइन की जांच करने की कोशिश कर रहा था), इसलिए वे उस लागत को छूट देते हैं।


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

7 नैनो सेकंड से ज्यादा लंबा क्या। यदि एक सामान्य कॉल 1 नैनो सेकेंड है जो कि सामान्य है यदि एक सामान्य कॉल 70 नैनो सेकंड है तो यह नहीं है।
मार्टिन यॉर्क

यदि आप समय को देखते हैं, तो मैंने पाया कि एक फ़ंक्शन जिसकी लागत 0.66ns इनलाइन है, एक सीधा फ़ंक्शन कॉल का ओवरहेड 4.8ns और एक वर्चुअल फ़ंक्शन 12.3ns (इनलाइन की तुलना में) था। आप अच्छी बात यह है कि यदि फ़ंक्शन स्वयं एक मिलीसेकंड खर्च करता है, तो 7 एनएस का मतलब कुछ भी नहीं है।
क्रैशवर्क

2
600 से अधिक चक्रों की तरह, लेकिन यह एक अच्छा बिंदु है। मैंने इसे समय से बाहर छोड़ दिया क्योंकि पाइप लाइन के बुलबुले और प्रोलॉग / एपिलॉग के कारण मुझे बस ओवरहेड में दिलचस्पी थी। Icache मिस सीधे फ़ंक्शन कॉल के लिए आसानी से होता है (क्सीनन के पास कोई icache ब्रांच प्रेडिक्टर नहीं है)।
क्रैटरवर्क्स

2
माइनर डिटेल, लेकिन "हालांकि यह एक समस्या के लिए विशिष्ट नहीं है ..." के बारे में यह एक अतिरिक्त पृष्ठ (या अगर यह एक पृष्ठ सीमा में गिरने के लिए होता है) के लिए आभासी प्रेषण के लिए एक बुरा है जो कैश में होना है - क्लास की वर्चुअल डिस्पैच टेबल के लिए।
टोनी डेलरो

19

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

बेशक, ये दंड महत्वपूर्ण हैं या नहीं यह आपके आवेदन पर निर्भर करता है, कितनी बार उन कोड पथों को निष्पादित किया जाता है, और आपके उत्तराधिकार पैटर्न।

हालांकि मेरी राय में, डिफ़ॉल्ट रूप से आभासी होने के नाते सब कुछ एक समस्या का एक कंबल समाधान है जिसे आप अन्य तरीकों से हल कर सकते हैं।

शायद आप देख सकते हैं कि कक्षाओं को कैसे डिज़ाइन / प्रलेखित / लिखा जाता है। आम तौर पर एक वर्ग के लिए हेडर को यह स्पष्ट करना चाहिए कि कौन से कार्य व्युत्पन्न वर्गों द्वारा ओवरराइड किए जा सकते हैं और उन्हें कैसे कहा जाता है। प्रोग्रामर लिखने के बाद यह दस्तावेज यह सुनिश्चित करने में सहायक होता है कि वे वर्चुअल रूप से सही तरीके से चिह्नित हैं।

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


सबसे बड़ी खोई गई अनुकूलन अलाइनिंग है, खासकर अगर वर्चुअल फ़ंक्शन अक्सर छोटा या खाली होता है।
ज़ैन लिंक्स

@Andrew: देखने का दिलचस्प बिंदु। मैं आपके अंतिम पैराग्राफ से कुछ हद तक असहमत हूं, हालांकि: यदि एक बेस क्लास में एक फ़ंक्शन है saveजो बेस क्लास में किसी फ़ंक्शन के विशिष्ट कार्यान्वयन पर निर्भर करता है write, तो यह मुझे लगता है कि या तो saveखराब कोडित है, या writeनिजी होना चाहिए।
मिनीक्वार

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

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

और एक आईकैश स्टाल वास्तव में गंभीर हो सकता है: मेरे परीक्षणों में 600 चक्र।
क्रैशवक्र्स

9

निर्भर करता है। :) (क्या आपको कुछ और उम्मीद थी?)

एक बार एक क्लास को वर्चुअल फंक्शन मिलने के बाद, यह अब POD डेटाटाइप नहीं हो सकता है, (यह या तो एक से पहले नहीं हो सकता है, जिस स्थिति में यह अंतर नहीं करेगा) और इससे अनुकूलन की पूरी श्रृंखला असंभव हो जाती है।

std :: copy () सादे POD प्रकारों पर एक साधारण याददाश्त का सहारा ले सकते हैं, लेकिन गैर-POD प्रकारों को अधिक सावधानी से संभालना होगा।

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

सबसे खराब स्थिति में, आप 5x धीमी गति से निष्पादन देख सकते हैं (यह संख्या एक विश्वविद्यालय परियोजना से ली गई है जिसे मैंने हाल ही में कुछ मानक पुस्तकालय वर्गों को फिर से लागू करने के लिए किया था। हमारे कंटेनर ने लगभग 5x का समय लिया था जब तक कि डेटा प्रकार संग्रहीत नहीं हो जाता। vtable)

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

हालाँकि, प्रदर्शन यहाँ आपका प्राथमिक विचार नहीं होना चाहिए। हर चीज को आभासी बनाना अन्य कारणों से एक सही समाधान नहीं है।

व्युत्पन्न वर्गों में अतिरंजित करने के लिए सब कुछ करने की अनुमति देने से वर्ग आक्रमणकारियों को बनाए रखना बहुत कठिन हो जाता है। एक वर्ग कैसे गारंटी देता है कि यह एक सुसंगत स्थिति में रहता है जब इसके किसी भी तरीके को किसी भी समय पुनर्परिभाषित किया जा सकता है?

सब कुछ आभासी बनाने से कुछ संभावित कीड़े समाप्त हो सकते हैं, लेकिन यह नए लोगों को भी पेश करता है।


7

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

हालाँकि, यदि आप ज़रूरतमंद नहीं हैं, तो अपने आप को ओवरहेड से लम्बरिंग करना, यह संभवतः बहुत दूर जा रहा है। और अधिकांश वर्गीय को विरासत में नहीं बनाया गया है - एक अच्छा आधार वर्ग बनाने के लिए अपने कार्यों को आभासी बनाने से अधिक आवश्यकता होती है।


अच्छा जवाब लेकिन, IMO, 2 हाफ में पर्याप्त सशक्त नहीं: खुद को ओवरहेड के साथ lumbering करना यदि आपको इसकी आवश्यकता नहीं है, तो काफी स्पष्ट रूप से, पागल - विशेष रूप से इस भाषा का उपयोग करते समय जिसका मंत्र है "आप जो भी करते हैं उसके लिए भुगतान न करें" 'टी यूज़। " डिफ़ॉल्ट रूप से सबकुछ आभासी बनाने तक कोई यह बताता है कि गैर-आभासी क्यों होना चाहिए / यह एक घृणित नीति है।
अंडरस्कोर_ड

5

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


कार्यान्वयन

#include <iostream>
#include <vector>

// virtual dispatch model...

struct Base
{
    virtual int f() const { return 1; }
};

struct Derived : Base
{
    virtual int f() const { return 2; }
};

// alternative: member variable encodes runtime type...

struct Type
{
    Type(int type) : type_(type) { }
    int type_;
};

struct A : Type
{
    A() : Type(1) { }
    int f() const { return 1; }
};

struct B : Type
{
    B() : Type(2) { }
    int f() const { return 2; }
};

struct Timer
{
    Timer() { clock_gettime(CLOCK_MONOTONIC, &from); }
    struct timespec from;
    double elapsed() const
    {
        struct timespec to;
        clock_gettime(CLOCK_MONOTONIC, &to);
        return to.tv_sec - from.tv_sec + 1E-9 * (to.tv_nsec - from.tv_nsec);
    }
};

int main(int argc)
{
  for (int j = 0; j < 3; ++j)
  {
    typedef std::vector<Base*> V;
    V v;

    for (int i = 0; i < 1000; ++i)
        v.push_back(i % 2 ? new Base : (Base*)new Derived);

    int total = 0;

    Timer tv;

    for (int i = 0; i < 100000; ++i)
        for (V::const_iterator i = v.begin(); i != v.end(); ++i)
            total += (*i)->f();

    double tve = tv.elapsed();

    std::cout << "virtual dispatch: " << total << ' ' << tve << '\n';

    // ----------------------------

    typedef std::vector<Type*> W;
    W w;

    for (int i = 0; i < 1000; ++i)
        w.push_back(i % 2 ? (Type*)new A : (Type*)new B);

    total = 0;

    Timer tw;

    for (int i = 0; i < 100000; ++i)
        for (W::const_iterator i = w.begin(); i != w.end(); ++i)
        {
            if ((*i)->type_ == 1)
                total += ((A*)(*i))->f();
            else
                total += ((B*)(*i))->f();
        }

    double twe = tw.elapsed();

    std::cout << "switched: " << total << ' ' << twe << '\n';

    // ----------------------------

    total = 0;

    Timer tw2;

    for (int i = 0; i < 100000; ++i)
        for (W::const_iterator i = w.begin(); i != w.end(); ++i)
            total += (*i)->type_;

    double tw2e = tw2.elapsed();

    std::cout << "overheads: " << total << ' ' << tw2e << '\n';
  }
}

प्रदर्शन के परिणाम

मेरे लिनक्स सिस्टम पर:

~/dev  g++ -O2 -o vdt vdt.cc -lrt
~/dev  ./vdt                     
virtual dispatch: 150000000 1.28025
switched: 150000000 0.344314
overhead: 150000000 0.229018
virtual dispatch: 150000000 1.285
switched: 150000000 0.345367
overhead: 150000000 0.231051
virtual dispatch: 150000000 1.28969
switched: 150000000 0.345876
overhead: 150000000 0.230726

यह बताता है कि एक इनलाइन टाइप-नंबर-स्विच्ड अप्रोच लगभग (1.28 - 0.23) / (0.344 - 0.23) = 9.2 गुना तेज है। बेशक, यह सटीक प्रणाली परीक्षण / संकलक झंडे और संस्करण आदि के लिए विशिष्ट है, लेकिन आम तौर पर सांकेतिक है।


टिप्पणियाँ रे विराटाल डिस्पैच

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


मैंने आपके कोड के बारे में एक प्रश्न पूछा था क्योंकि मेरे पास g++/ clangऔर का उपयोग करके कुछ "अजीब" परिणाम हैं -lrt। मुझे लगा कि भविष्य के पाठकों के लिए यहाँ ध्यान देने योग्य बात है।
होल्ट

@Holt: रहस्यपूर्ण परिणामों को देखते हुए अच्छा प्रश्न! अगर मुझे आधा मौका मिलता है तो मैं इसे कुछ दिनों में करीब से देखूंगा। चीयर्स।
टोनी डेलरो

3

अधिकांश परिदृश्यों में अतिरिक्त लागत लगभग कुछ भी नहीं है। (क्षमा करें, इसका दूसरा अर्थ यह भी है)। स्खलित पहले से ही समझदार रिश्तेदार उपायों को पोस्ट किया है।

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


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

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


वंचित उदाहरण: एक लाख छोटे तत्वों की एक सरणी पर एक खाली आभासी विध्वंसक कम से कम 4MB डेटा के माध्यम से आपके कैश को थ्रेश कर सकता है। यदि उस विध्वंसक को दूर किया जा सकता है, तो डेटा को छुआ नहीं जाएगा।

लाइब्रेरी कोड लिखते समय, इस तरह के विचार समय से पहले होते हैं। आप कभी नहीं जानते कि आपके फंक्शन में कितने लूप लगाए जाएंगे।


2

जबकि बाकी सभी वर्चुअल तरीकों और इस तरह के प्रदर्शन के बारे में सही हैं, मुझे लगता है कि वास्तविक समस्या यह है कि क्या टीम C ++ में वर्चुअल कीवर्ड की परिभाषा के बारे में जानती है।

इस कोड पर विचार करें, आउटपुट क्या है?

#include <stdio.h>

class A
{
public:
    void Foo()
    {
        printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
        printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

यहां कुछ भी आश्चर्यजनक नहीं है:

A::Foo()
B::Foo()
A::Foo()

जैसा कि कुछ भी आभासी नहीं है। यदि A और B दोनों वर्गों में Foo के सामने वर्चुअल कीवर्ड जोड़ा जाता है, तो हम इसे आउटपुट के लिए प्राप्त करते हैं:

A::Foo()
B::Foo()
B::Foo()

बहुत ज्यादा हर कोई उम्मीद करता है।

अब, आपने उल्लेख किया कि बग हैं क्योंकि कोई व्यक्ति वर्चुअल कीवर्ड जोड़ना भूल गया है। तो इस कोड पर विचार करें (जहां वर्चुअल कीवर्ड को ए, लेकिन बी क्लास में नहीं जोड़ा गया है)। फिर आउटपुट क्या है?

#include <stdio.h>

class A
{
public:
    virtual void Foo()
    {
        printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
        printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

उत्तर: वर्चुअल कीवर्ड को B में जोड़े जाने के समान है? कारण यह है कि B :: Foo के लिए हस्ताक्षर बिल्कुल A :: Foo () से मेल खाते हैं और क्योंकि A का Foo आभासी है, इसलिए B's है।

अब उस मामले पर विचार करें जहां बी का फू आभासी है और ए का नहीं है। फिर आउटपुट क्या है? इस मामले में, आउटपुट है

A::Foo()
B::Foo()
A::Foo()

वर्चुअल कीवर्ड पदानुक्रम में नीचे की ओर काम करता है, ऊपर की ओर नहीं। यह कभी भी आधार वर्ग के तरीकों को आभासी नहीं बनाता है। पहली बार एक आभासी विधि पदानुक्रम में सामने आई है जब बहुरूपता शुरू होती है। बाद की कक्षाओं के लिए कोई रास्ता नहीं है कि पिछली कक्षाओं को आभासी तरीके से बनाया जाए।

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

इसलिए यदि आपके पास वर्चुअल कीवर्ड को हटाने का नियम है, तो इसका इच्छित प्रभाव नहीं हो सकता है।

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


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

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

1

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

मैं इस कारण से आपके सुझाव के विरुद्ध सुझाव दूंगा, लेकिन यदि यह बग्स को रोकने में आपकी मदद करता है तो यह व्यापार बंद होने के लायक हो सकता है। मैं मदद नहीं कर सकता, लेकिन लगता है कि वहाँ कुछ मध्यम जमीन है कि खोजने लायक है, हालांकि होना चाहिए।


-1

आभासी पद्धति को कॉल करने के लिए बस कुछ अतिरिक्त अतिरिक्त निर्देश की आवश्यकता होगी।

लेकिन मुझे नहीं लगता कि आपको चिंता है कि मज़ा (इंट, ए, बी) में मज़ा () की तुलना में अतिरिक्त 'पुश' निर्देशों का एक जोड़ा है। तो जब तक आप विशेष स्थिति में होते हैं, तब तक वर्चुअल के बारे में चिंता न करें और देखें कि यह वास्तव में समस्याओं की ओर ले जाता है।

PS यदि आपके पास एक वर्चुअल विधि है, तो सुनिश्चित करें कि आपके पास एक वर्चुअल विध्वंसक है। इस तरह आप संभावित समस्याओं से बच जाएंगे


'Xtofl' और 'Tom' टिप्पणियों के जवाब में। मैंने 3 कार्यों के साथ छोटे परीक्षण किए:

  1. वास्तविक
  2. साधारण
  3. 3 अंतर मापदंडों के साथ सामान्य

मेरा परीक्षण एक साधारण पुनरावृत्ति था:

for(int it = 0; it < 100000000; it ++) {
    test.Method();
}

और यहाँ परिणाम:

  1. 3,913 सेकेंड
  2. 3,873 सेकंड
  3. 3,970 सेकंड

यह VC ++ द्वारा डिबग मोड में संकलित किया गया था। मैंने प्रति विधि केवल 5 परीक्षण किए और माध्य मान की गणना की (इसलिए परिणाम बहुत गलत हो सकते हैं) ... किसी भी तरह, मान लगभग 100 मिलियन कॉल के बराबर हैं। और 3 अतिरिक्त पुश / पॉप के साथ विधि धीमी थी।

मुख्य बिंदु यह है कि यदि आप पुश / पॉप के साथ सादृश्य पसंद नहीं करते हैं, तो अतिरिक्त के बारे में सोचें यदि आपके कोड में / और है? क्या आप सीपीयू पाइपलाइन के बारे में सोचते हैं जब आप अतिरिक्त जोड़ते हैं / नहीं तो ;-) इसके अलावा, आपको यह कभी नहीं पता होगा कि सीपीयू कोड क्या चल रहा होगा ... सामान्य कंपाइलर एक सीपीयू के लिए कोड अधिक इष्टतम उत्पन्न कर सकता है और एक दूसरे के लिए कम इष्टतम ( इंटेल) सी ++ कंपाइलर )


2
अतिरिक्त asm सिर्फ एक पृष्ठ गलती को ट्रिगर कर सकता है (जो कि गैर-आभासी कार्यों के लिए नहीं होगा) - मुझे लगता है कि आप समस्या का अत्यधिक निरीक्षण करते हैं।
xtofl

2
+1 से xtofl की टिप्पणी। वर्चुअल फ़ंक्शंस अप्रत्यक्ष रूप से पेश करते हैं, जो पाइपलाइन "बुलबुले" का परिचय देते हैं और कैशिंग व्यवहार को प्रभावित करते हैं।
टॉम

1
डिबग मोड में कुछ भी समय व्यर्थ है। MSVC डिबग मोड में बहुत धीमा कोड बनाता है, और लूप ओवरहेड शायद अधिकांश अंतर छिपाता है। यदि आप उच्च प्रदर्शन के लिए लक्ष्य कर रहे हैं, तो हाँ आपको कम से कम इस बारे में सोचना चाहिए कि क्या फास्ट पथ में शाखाएँ हैं / हैं। निम्न-स्तरीय x86 प्रदर्शन अनुकूलन के बारे में अधिक जानने के लिए agner.org/optimize देखें । ( X86 टैग विकी
पीटर कॉर्ड्स

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

यह माइक्रोबेनचर्च के सामान्य जाल में गिर जाता है: यह तेजी से दिखता है जब शाखा-भविष्यवक्ता गर्म होते हैं और कुछ और नहीं चल रहा है। callप्रत्यक्ष के मुकाबले अप्रत्यक्ष के लिए गलत ओवरहेड अधिक है call। (और हां, सामान्य callनिर्देशों को भी पूर्वानुमान की आवश्यकता है। इस चरण को हटाने से पहले लाने के लिए भ्रूण के चरण का अगला पता जानना होगा, इसलिए उसे निर्देश पते के बजाय वर्तमान ब्लॉक पते के आधार पर अगले भ्रूण ब्लॉक का अनुमान लगाना होगा। भविष्यवाणी के रूप में जहां इस ब्लॉक में एक शाखा निर्देश है ...)
पीटर कॉर्ड्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.