2011-01-26 21 views
7

तो मान लीजिए कि मैं कक्षाओं की एक श्रृंखला बनाना चाहता हूं जिसमें प्रत्येक के साथ एक ही सदस्य-कार्य हो। के समारोहआभासी कार्यों से बचें

void doYourJob(); 

मैं अंत में एक ही कंटेनर में इन सभी वर्गों रखना चाहते हैं फोन ताकि मैं उन के माध्यम से पाश कर सकते हैं और प्रत्येक प्रदर्शन 'doYourJob()'

स्पष्ट समाधान एक बनाने के लिए है करते हैं समारोह के साथ अमूर्त वर्ग

virtual void doYourJob(); 

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

क्या वर्चुअल फ़ंक्शन के साथ एक अमूर्त वर्ग का उपयोग करने से बचने का कोई तरीका है या क्या मुझे इसे चूसना होगा?

+1

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

उत्तर

6

वर्चुअल फ़ंक्शंस का अधिक खर्च नहीं होता है। वे एक अप्रत्यक्ष कॉल हैं, मूल रूप से एक फ़ंक्शन पॉइंटर की तरह। What is the performance cost of having a virtual method in a C++ class?

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

4

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

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

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

+3

'dynamic_cast' केवल उन प्रकारों के लिए काम करता है जिनमें कम से कम एक वर्चुअल फ़ंक्शन है, और यदि ऐसा है तो आप 'doYourJob()' के लिए उस वर्चुअल फ़ंक्शन का उपयोग करके बहुत बेहतर हैं। – templatetypedef

+0

यह [कोड] वर्ग base_class {वर्चुअल शून्य doYourJob() {}} [कोड] का उपयोग करने जितना आसान होगा या इसे अधिक कुशल बनाने के लिए कोई तरीका है – hedgehogrider

+1

यह वास्तव में इतना आसान है। आप 'आभासी शून्य doYourJob() = 0;' इसके बजाय, उप-वर्ग ** ** को डिफ़ॉल्ट नो-ऑप कार्यान्वयन प्राप्त करने के बजाय ** कार्यान्वयन प्रदान करना चाहेंगे। किसी भी मामले में, लागत यह है कि आप प्रत्येक वर्ग के 'आकार' में एक पॉइंटर जोड़ते हैं, और कॉल समय पर एक या दो संकेतों को बनाना होता है। यह कुछ नैनोसेकंद है। –

1

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

मैं दृढ़ता से सुझाव देता हूं कि पहले विरासत का उपयोग करने के सरल, सरल दृष्टिकोण को आजमाएं। यदि यह पर्याप्त तेज़ है, तो यह बहुत अच्छा है! हो गया। यदि ऐसा नहीं है, तो कुछ अन्य योजना का उपयोग करने का प्रयास करें। लागत के कारण कभी भी उपयोगी भाषा सुविधा से बचें जब तक कि आपके पास यह सुझाव देने के लिए कोई अच्छा कड़ी सबूत न हो कि लागत बहुत बढ़िया है।

8

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


कार्यान्वयन

#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'; 
    } 
} 

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

मेरी Linux सिस्टम पर:

~/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 जितनी जल्दी हो सके। बेशक, यह सटीक सिस्टम परीक्षण/कंपाइलर झंडे & संस्करण इत्यादि के लिए विशिष्ट है, लेकिन आम तौर पर संकेतक है।


टिप्पणियाँ आभासी प्रेषण

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

+0

कोड '+lrt' के साथ क्लैंग ++ के बिना इतना तेज़ क्यों है? http://coliru.stacked-crooked.com/a/83ac0dc7d15b4747 – Gabriel

+0

ध्वज '-lrt', लाइब्रेरी "आरटी" के साथ लिंक क्या है ?? – Gabriel

+0

@ गैब्रियल: हाँ - यह librt.so - "आरटी" को वास्तविक समय के लिए खड़ा करेगा, और इसमें 'clock_gettime' फ़ंक्शन शामिल है। –

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