RTTI कितना महंगा?


152

मैं समझता हूं कि RTTI का उपयोग करने से एक संसाधन प्रभावित होता है, लेकिन यह कितना बड़ा है? हर जगह मैंने देखा है कि "RTTI महंगा है," लेकिन उनमें से कोई भी वास्तव में स्मृति, प्रोसेसर समय या गति के संबंध में कोई भी मानक या मात्रात्मक डेटा नहीं देता है।

तो, RTTI कितना महंगा है? मैं इसे एक एम्बेडेड सिस्टम पर उपयोग कर सकता हूं जहां मेरे पास केवल 4MB RAM है, इसलिए हर बिट गिना जाता है।

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

मेरी समझ से, dynamic_castआरटीटीआई का उपयोग करता है, इसलिए मैं सोच रहा था कि सीमित प्रणाली पर इसका उपयोग कितना संभव होगा।


1
आपके संपादन के बाद - बहुत बार जब मैं खुद को कई गतिशील कास्ट करता हुआ पाता हूं तो मुझे पता चलता है कि विज़िटर पैटर्न का उपयोग करने से चीजें फिर से बाहर हो जाती हैं। क्या वह आपके लिए काम कर सकता है?
philsquared

4
मैं इसे इस तरह रखूँगा - मैंने अभी dynamic_castC ++ में उपयोग करना शुरू किया , और अब, 10 में से 9 बार जब मैं डिबगर के साथ प्रोग्राम को "ब्रेक" करता हूं, तो यह आंतरिक डायनामिक-कास्ट फ़ंक्शन के अंदर टूट जाता है। यह बहुत धीमी है।
user541686

3
आरटीटीआई = "रन-टाइम प्रकार की जानकारी", वैसे।
नौमेनन

जवाबों:


115

संकलक के बावजूद, आप हमेशा रनटाइम पर बचत कर सकते हैं यदि आप कर सकते हैं

if (typeid(a) == typeid(b)) {
  B* ba = static_cast<B*>(&a);
  etc;
}

के बजाय

B* ba = dynamic_cast<B*>(&a);
if (ba) {
  etc;
}

पूर्व में केवल एक तुलना शामिल है std::type_info; उत्तरार्द्ध आवश्यक रूप से एक वंशानुक्रम वृक्ष और तुलनाओं को ट्रेस करना शामिल है।

अतीत कि ... जैसा कि सभी कहते हैं, संसाधन उपयोग विशिष्ट कार्यान्वयन है।

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

मैंने हाल ही में जीसीसी में आरटीटीआई में अनुसंधान का एक समूह बनाया।

tl; dr: GCC में RTTI नगण्य स्थान का उपयोग करता है और typeid(a) == typeid(b)बहुत तेज है, कई प्लेटफार्मों (लिनक्स, बीएसडी और शायद एम्बेडेड प्लेटफार्मों पर है, लेकिन mingw32 नहीं)। यदि आप जानते हैं कि आप हमेशा एक धन्य मंच पर रहेंगे, तो आरटीटीआई मुफ्त में बहुत करीब है।

गंभीर विवरण:

जीसीसी एक विशेष "वेंडर-न्यूट्रल" C ++ ABI [1] का उपयोग करना पसंद करता है, और हमेशा लिनक्स और BSD के लक्ष्य [2] के लिए इस ABI का उपयोग करता है। ऐसे प्लेटफार्मों के लिए जो इस ABI का समर्थन करते हैं और यह भी कमजोर लिंकेज, typeid()गतिशील लिंकिंग सीमाओं के पार भी, प्रत्येक प्रकार के लिए एक सुसंगत और अद्वितीय वस्तु देता है। आप परीक्षण कर सकते हैं &typeid(a) == &typeid(b), या केवल इस तथ्य पर भरोसा कर सकते हैं कि पोर्टेबल परीक्षण typeid(a) == typeid(b)वास्तव में आंतरिक रूप से केवल एक सूचक की तुलना करता है।

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

मैं क्या कर सकता हूं, जीसीसी द्वारा उपयोग की जाने वाली आरटीटीआई संरचनाएं (ये सभी उपवर्ग हैं std::type_info) केवल नाम से अलग, प्रत्येक प्रकार के लिए कुछ बाइट्स रखती हैं। यह मेरे लिए स्पष्ट नहीं है कि क्या नाम आउटपुट कोड में भी मौजूद हैं -fno-rtti। किसी भी तरह, संकलित बाइनरी के आकार में परिवर्तन को रनटाइम मेमोरी के उपयोग में परिवर्तन को प्रतिबिंबित करना चाहिए।

एक त्वरित प्रयोग (Ubuntu 10.04 64-बिट पर GCC 4.4.3 का उपयोग करना) दर्शाता है कि -fno-rttiवास्तव में कुछ सौ बाइट्स द्वारा एक साधारण परीक्षण कार्यक्रम के द्विआधारी आकार में वृद्धि होती है। यह -gऔर के संयोजनों में लगातार होता है -O3। मुझे यकीन नहीं है कि आकार क्यों बढ़ेगा; एक संभावना यह है कि GCC का STL कोड बिना RTTI के अलग व्यवहार करता है (क्योंकि अपवाद काम नहीं करेगा)।

[१] इसे इटेनियम C ++ ABI के नाम से जाना जाता है, जो http://www.codesourcery.com/public/cxx-abi/abi.html पर प्रलेखित है । नाम बुरी तरह से भ्रमित कर रहे हैं: नाम मूल विकास वास्तुकला को संदर्भित करता है, हालांकि एबीआई विनिर्देश i686 / x86_64 सहित कई आर्किटेक्चर पर काम करता है। जीसीसी के आंतरिक स्रोत और एसटीएल कोड में टिप्पणियाँ पहले इस्तेमाल किए गए "पुराने" के विपरीत "नई" एबीआई के रूप में इटेनियम को संदर्भित करती हैं। इससे भी बदतर, "नया" / इटेनियम एबीआई के माध्यम से उपलब्ध सभी संस्करणों को संदर्भित करता है -fabi-version; "पुराने" ABI ने इस संस्करण की भविष्यवाणी की। जीसीसी ने संस्करण 3.0 में इटेनियम / संस्करण / "नया" एबीआई को अपनाया; "पुराने" ABI का उपयोग 2.95 और पूर्व में किया गया था, अगर मैं उनके चैंज को सही ढंग से पढ़ रहा हूं।

[२] मुझे std::type_infoप्लेटफ़ॉर्म द्वारा किसी भी संसाधन लिस्टिंग ऑब्जेक्ट स्थिरता का पता नहीं चल सका । संकलक के लिए मेरे पास पहुंच थी, मैंने निम्नलिखित का उपयोग किया echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES:। इस मैक्रो नियंत्रण के व्यवहार operator==के लिए std::type_infoजीसीसी के एसटीएल में, जीसीसी 3.0 के रूप में। मैंने पाया कि mingw32-gcc ने विंडोज C ++ ABI का पालन किया, जहाँ std::type_infoऑब्जेक्ट DLL में एक प्रकार के लिए अद्वितीय नहीं हैं; कवर के तहत typeid(a) == typeid(b)कॉल strcmp। मैं अनुमान लगाता हूं कि एवीआर जैसे एकल-प्रोग्राम एम्बेडेड लक्ष्यों पर, जहां लिंक के खिलाफ कोई कोड नहीं है, std::type_infoऑब्जेक्ट हमेशा स्थिर होते हैं।


6
अपवाद RTTI के बिना काम करते हैं। (आप को फेंकने की अनुमति है intऔर उस :) में कोई व्यवहार्य नहीं है)
बिली ओनली

3
@ डेड्यूलाइटर: और फिर भी, जब मैं अपने कंपाइलर में आरटीटीआई को बंद करता हूं, तो वे ठीक काम करते हैं। आप को निराश करने के लिए क्षमा कीजिए।
बिली ओयल

5
अपवाद-हैंडलिंग तंत्र को कुछ बुनियादी आवश्यकताओं को पूरा करने के लिए किसी भी प्रकार के साथ काम करने में सक्षम होना चाहिए। आप आरटीटीआई के बिना मॉड्यूल सीमाओं के पार मनमाने प्रकार के अपवादों को फेंकने और पकड़ने के तरीके को संभालने के लिए सुझाव देने के लिए स्वतंत्र हैं । कृपया विचार करें कि अप और डाउन-कास्टिंग की आवश्यकता है।
Deduplicator

15
टाइपिड (ए) == टाइपिड (बी) बी * बा = डायनामिक_कास्ट <बी *> ((ए) के समान नहीं है। व्युत्पन्न वर्ग के पेड़ पर एक यादृच्छिक स्तर के रूप में कई विरासत वाले ऑब्जेक्ट पर इसे आज़माएं और आपको टाइपिड () == टाइपिड () एक सकारात्मक उपज नहीं देगा। डायनेमिक_कास्ट वास्तविक के लिए वंशानुक्रम वृक्ष की खोज करने का एकमात्र तरीका है। RTTI को अक्षम करके संभावित बचत के बारे में सोचना बंद करें और बस इसका उपयोग करें। यदि आप क्षमता से अधिक हैं तो अपने कोड ब्लोट का अनुकूलन करें। इनर लूप या किसी अन्य प्रदर्शन महत्वपूर्ण कोड के अंदर डायनामिक_का उपयोग करने से बचने की कोशिश करें और आप ठीक हो जाएंगे।
मिस्टिककॉडर

3
@mcoder यही कारण है कि लेख स्पष्ट रूप से बताता है कि the latter necessarily involves traversing an inheritance tree plus comparisons। @CoryB आप इसे "वहन" कर सकते हैं जब आपको संपूर्ण वंशानुक्रम वृक्ष से कास्टिंग का समर्थन करने की आवश्यकता नहीं होती है। उदाहरण के लिए यदि आप एक संग्रह में टाइप X के सभी आइटम ढूंढना चाहते हैं, लेकिन उन लोगों से नहीं जो एक्स से निकलते हैं, तो आपको जो उपयोग करना चाहिए वह पूर्व है। यदि आपको सभी व्युत्पन्न उदाहरणों को खोजने की आवश्यकता है, तो आपको बाद का उपयोग करना होगा।
Aidiakapi

48

शायद ये आंकड़े मदद करेंगे।

मैं इसका उपयोग करके एक त्वरित परीक्षण कर रहा था:

  • जीसीसी क्लॉक () + XCode का प्रोफाइलर।
  • 100,000,000 लूप पुनरावृत्तियों।
  • 2 एक्स 2.66 गीगाहर्ट्ज़ डुअल-कोर इंटेल एक्सॉन।
  • विचाराधीन वर्ग एकल आधार वर्ग से लिया गया है।
  • टाइपिड ()। नाम () रिटर्न "N12fastdelegate13FastDelegate1IivEE"

5 मामलों का परीक्षण किया गया:

1) dynamic_cast< FireType* >( mDelegate )
2) typeid( *iDelegate ) == typeid( *mDelegate )
3) typeid( *iDelegate ).name() == typeid( *mDelegate ).name()
4) &typeid( *iDelegate ) == &typeid( *mDelegate )
5) { 
       fastdelegate::FastDelegateBase *iDelegate;
       iDelegate = new fastdelegate::FastDelegate1< t1 >;
       typeid( *iDelegate ) == typeid( *mDelegate )
   }

5 सिर्फ मेरा वास्तविक कोड है, जैसा कि मुझे जाँचने से पहले उस प्रकार की एक वस्तु बनाने की आवश्यकता है अगर यह मेरे पास पहले से ही है।

अनुकूलन के बिना

जिसके लिए परिणाम थे (मैंने कुछ रन औसत किए हैं):

1)  1,840,000 Ticks (~2  Seconds) - dynamic_cast
2)    870,000 Ticks (~1  Second)  - typeid()
3)    890,000 Ticks (~1  Second)  - typeid().name()
4)    615,000 Ticks (~1  Second)  - &typeid()
5) 14,261,000 Ticks (~23 Seconds) - typeid() with extra variable allocations.

तो निष्कर्ष यह होगा:

  • अनुकूलन के बिना सरल कास्ट मामलों के लिए typeid()दो बार से अधिक तेजी से है dyncamic_cast
  • एक आधुनिक मशीन पर दोनों के बीच का अंतर लगभग 1 नैनोसेकंड (एक मिलीसेकंड का मिलियन) है।

अनुकूलन के साथ (-O)

1)  1,356,000 Ticks - dynamic_cast
2)     76,000 Ticks - typeid()
3)     76,000 Ticks - typeid().name()
4)     75,000 Ticks - &typeid()
5)     75,000 Ticks - typeid() with extra variable allocations.

तो निष्कर्ष यह होगा:

  • अनुकूलन के साथ सरल कास्ट मामलों के लिए, typeid()की तुलना में लगभग x20 तेज है dyncamic_cast

चार्ट

यहाँ छवि विवरण दर्ज करें

कोड

जैसा कि टिप्पणियों में अनुरोध किया गया है, कोड नीचे है (थोड़ा गड़बड़ है, लेकिन काम करता है)। H FastDelegate.h ’ यहां से उपलब्ध है

#include <iostream>
#include "FastDelegate.h"
#include "cycle.h"
#include "time.h"

// Undefine for typeid checks
#define CAST

class ZoomManager
{
public:
    template < class Observer, class t1 >
    void Subscribe( void *aObj, void (Observer::*func )( t1 a1 ) )
    {
        mDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        std::cout << "Subscribe\n";
        Fire( true );
    }
    
    template< class t1 >
    void Fire( t1 a1 )
    {
        fastdelegate::FastDelegateBase *iDelegate;
        iDelegate = new fastdelegate::FastDelegate1< t1 >;
        
        int t = 0;
        ticks start = getticks();
        
        clock_t iStart, iEnd;
        
        iStart = clock();
        
        typedef fastdelegate::FastDelegate1< t1 > FireType;
        
        for ( int i = 0; i < 100000000; i++ ) {
        
#ifdef CAST
                if ( dynamic_cast< FireType* >( mDelegate ) )
#else
                // Change this line for comparisons .name() and & comparisons
                if ( typeid( *iDelegate ) == typeid( *mDelegate ) )
#endif
                {
                    t++;
                } else {
                    t--;
                }
        }
        
        iEnd = clock();
        printf("Clock ticks: %i,\n", iEnd - iStart );
        
        std::cout << typeid( *mDelegate ).name()<<"\n";
        
        ticks end = getticks();
        double e = elapsed(start, end);
        std::cout << "Elasped: " << e;
    }
    
    template< class t1, class t2 >
    void Fire( t1 a1, t2 a2 )
    {
        std::cout << "Fire\n";
    }
    
    fastdelegate::FastDelegateBase *mDelegate;
};

class Scaler
{
public:
    Scaler( ZoomManager *aZoomManager ) :
        mZoomManager( aZoomManager ) { }
    
    void Sub()
    {
        mZoomManager->Subscribe( this, &Scaler::OnSizeChanged );
    }
    
    void OnSizeChanged( int X  )
    {
        std::cout << "Yey!\n";        
    }
private:
    ZoomManager *mZoomManager;
};

int main(int argc, const char * argv[])
{
    ZoomManager *iZoomManager = new ZoomManager();
    
    Scaler iScaler( iZoomManager );
    iScaler.Sub();
        
    delete iZoomManager;

    return 0;
}

1
बेशक, गतिशील कास्ट अधिक सामान्य है - यह काम करता है यदि आइटम अधिक व्युत्पन्न है। उदाहरण के लिए class a {}; class b : public a {}; class c : public b {};जब लक्ष्य का एक उदाहरण है cजब वर्ग के लिए परीक्षण इच्छा काम ठीक bसे dynamic_cast, लेकिन साथ नहीं typeidसमाधान। फिर भी वाजिब है, +1
बिली ओनेल

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

3
@ क्यूबा: फिर बेंचमार्क फर्जी है। यह अनुकूलन के साथ बेंचमार्क करने का एक कारण नहीं है; बेहतर बेंचमार्क लिखने का एक कारण है।
बिली ओनली

3
फिर भी, यह एक विफलता है। "अनुकूलन के साथ सरल कास्ट मामलों के लिए, टाइपिड () dyncamic_cast की तुलना में लगभग x20 तेज है।" वे एक ही काम नहीं करते। एक कारण है कि डायनेमिक_कास्ट धीमा है।
मिस्टिककॉडर

1
@ कुबाओबर: कुल +1। यह बहुत क्लासिक है। और यह उस चक्र संख्या की दृष्टि से स्पष्ट होना चाहिए जो ऐसा हुआ था।
v.oddou

38

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

छद्म-सी ++ में उदाहरण के लिए:

struct Base
{
    virtual ~Base() {}
};

struct Derived
{
    virtual ~Derived() {}
};


int main()
{
    Base *d = new Derived();
    const char *name = typeid(*d).name(); // C++ way

    // faked up way (this won't actually work, but gives an idea of what might be happening in some implementations).
    const vtable *vt = reinterpret_cast<vtable *>(d);
    type_info *ti = vt->typeinfo;
    const char *name = ProcessRawName(ti->name);       
}

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

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


4
+1 वास्तव में यह समझाने के लिए कि आरटीटीआई का उपयोग करना एक बुरा डिजाइन निर्णय क्यों माना जाता है, जो मेरे लिए पहले बिल्कुल स्पष्ट नहीं था।
अगस्त

6
यह उत्तर C ++ की शक्ति की निम्न स्तर की समझ है। "सामान्य रूप से" और "अधिकांश कार्यान्वयन में" उदारतापूर्वक उपयोग किए जाने का अर्थ है कि आप इस बारे में नहीं सोच रहे हैं कि भाषाओं का उपयोग कैसे करना है। वर्चुअल फ़ंक्शन और आरटीटीआई को फिर से लागू करना जवाब नहीं है। RTTI इसका जवाब है। कभी-कभी आप केवल यह जानना चाहते हैं कि क्या वस्तु एक निश्चित प्रकार है। इसलिए यह वहाँ है! तो आप कुछ type_info स्ट्रक्चर्स के लिए कुछ KB RAM खो देते हैं। Gee ...
रहस्यवादी

16

खैर, प्रोफाइलर कभी झूठ नहीं बोलता।

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

ऐसा लगता है कि RTTI है महंगा, और अधिक एक बराबर की तुलना में महंगा ifबयान या एक साधारण switchसी ++ में एक आदिम चर पर। तो S.Lott का जवाब पूरी तरह से सही नहीं है, वहाँ है RTTI के लिए अतिरिक्त लागत, और यह है नहीं बस की वजह से एक होने के ifबयान मिश्रण में। इसकी वजह है कि RTTI बहुत महंगा है।

यह परीक्षण Apple LLVM 5.0 कंपाइलर पर किया गया था, जिसमें स्टॉक ऑप्टिमाइज़ेशन (डिफ़ॉल्ट रिलीज़ मोड सेटिंग्स) चालू थे।

इसलिए, मेरे 2 कार्य नीचे हैं, जिनमें से प्रत्येक किसी वस्तु के ठोस प्रकार को या तो 1) आरटीटीआई या 2) एक साधारण स्विच के रूप में दर्शाता है। यह ऐसा 50,000,000 बार करता है। आगे की हलचल के बिना, मैं आपके लिए 50,000,000 रन के सापेक्ष रनटाइम प्रस्तुत करता हूं।

यहाँ छवि विवरण दर्ज करें

यह सही है, 94% रनटाइम dynamicCastsलिया । जबकि ब्लॉक ने केवल 3.3% लिया ।regularSwitch

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

उस ने कहा, ऐसा करने से आपकी ओओपी प्रथाओं को गड़बड़ नहीं होना चाहिए .. इसका उपयोग केवल तब किया जाता है जब टाइप जानकारी बस उपलब्ध नहीं होती है और आप अपने आप को आरटीटीआई का उपयोग करते हुए पाते हैं।

#include <stdio.h>
#include <vector>
using namespace std;

enum AnimalClassTypeTag
{
  TypeAnimal=1,
  TypeCat=1<<2,TypeBigCat=1<<3,TypeDog=1<<4
} ;

struct Animal
{
  int typeTag ;// really AnimalClassTypeTag, but it will complain at the |= if
               // at the |='s if not int
  Animal() {
    typeTag=TypeAnimal; // start just base Animal.
    // subclass ctors will |= in other types
  }
  virtual ~Animal(){}//make it polymorphic too
} ;

struct Cat : public Animal
{
  Cat(){
    typeTag|=TypeCat; //bitwise OR in the type
  }
} ;

struct BigCat : public Cat
{
  BigCat(){
    typeTag|=TypeBigCat;
  }
} ;

struct Dog : public Animal
{
  Dog(){
    typeTag|=TypeDog;
  }
} ;

typedef unsigned long long ULONGLONG;

void dynamicCasts(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( dynamic_cast<Dog*>( an ) )
        dogs++;
      else if( dynamic_cast<BigCat*>( an ) )
        bigcats++;
      else if( dynamic_cast<Cat*>( an ) )
        cats++;
      else //if( dynamic_cast<Animal*>( an ) )
        animals++;
    }
  }

  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;

}

//*NOTE: I changed from switch to if/else if chain
void regularSwitch(vector<Animal*> &zoo, ULONGLONG tests)
{
  ULONGLONG animals=0,cats=0,bigcats=0,dogs=0;
  for( ULONGLONG i = 0 ; i < tests ; i++ )
  {
    for( Animal* an : zoo )
    {
      if( an->typeTag & TypeDog )
        dogs++;
      else if( an->typeTag & TypeBigCat )
        bigcats++;
      else if( an->typeTag & TypeCat )
        cats++;
      else
        animals++;
    }
  }
  printf( "%lld animals, %lld cats, %lld bigcats, %lld dogs\n", animals,cats,bigcats,dogs ) ;  

}

int main(int argc, const char * argv[])
{
  vector<Animal*> zoo ;

  zoo.push_back( new Animal ) ;
  zoo.push_back( new Cat ) ;
  zoo.push_back( new BigCat ) ;
  zoo.push_back( new Dog ) ;

  ULONGLONG tests=50000000;

  dynamicCasts( zoo, tests ) ;
  regularSwitch( zoo, tests ) ;
}

13

मानक तरीका:

cout << (typeid(Base) == typeid(Derived)) << endl;

मानक RTTI महंगा है क्योंकि यह एक अंतर्निहित स्ट्रिंग तुलना करने पर निर्भर करता है और इस प्रकार RTTI की गति वर्ग के नाम की लंबाई के आधार पर भिन्न हो सकती है।

स्ट्रिंग तुलना का उपयोग करने का कारण यह पुस्तकालय / DLL सीमाओं के पार लगातार काम करना है। यदि आप अपने आवेदन को सांख्यिकीय रूप से बनाते हैं और / या आप कुछ संकलक का उपयोग कर रहे हैं तो आप संभवतः उपयोग कर सकते हैं:

cout << (typeid(Base).name() == typeid(Derived).name()) << endl;

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

cout << (&typeid(Base) == &typeid(Derived)) << endl;

हालांकि आप सुरक्षित रूप से एक हाइब्रिड का उपयोग कर सकते हैं जो कि टाइप मैच होने पर बहुत तेज़ होगा, और बेजोड़ प्रकारों के लिए सबसे खराब स्थिति होगी:

cout << ( typeid(Base).name() == typeid(Derived).name() || 
          typeid(Base) == typeid(Derived) ) << endl;

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

इसे ऑप्टिमाइज़ करने का सबसे सुरक्षित तरीका यह है कि आप अपने बेस क्लास के हिस्से के रूप में अपने स्वयं के टाइपिड को इंट (या एनम टाइप: इंट) के रूप में लागू करें और क्लास के प्रकार को निर्धारित करने के लिए उपयोग करें, और उसके बाद ही static_cast का उपयोग करें <> या पुन: व्याख्या करें- < >

मेरे लिए यह अन्तर लगभग 15 गुना है जो कि अनूसूचित MS VS 2005 C ++ SP1 पर है।


2
"मानक RTTI महंगा है क्योंकि यह एक अंतर्निहित स्ट्रिंग तुलना करने पर निर्भर करता है" - नहीं, इस बारे में "मानक" कुछ भी नहीं है; यह सिर्फ यह है कि आपके क्रियान्वयन का typeid::operatorकाम कैसा है । उदाहरण के लिए, एक समर्थित प्लेटफॉर्म पर जीसीसी, पहले से ही char *एस की तुलना का उपयोग करता है , हमारे बिना इसे मजबूर करने के लिए - gcc.gnu.org/onbuildocs/gcc-4.6.3/libstdc++/api/… । निश्चित रूप से, आपका रास्ता MSVC आपके प्लेटफ़ॉर्म पर डिफ़ॉल्ट की तुलना में बहुत बेहतर व्यवहार करता है, इसलिए कुदोस, और मुझे नहीं पता कि "कुछ लक्ष्य" जो मूल रूप से पॉइंटर्स का उपयोग करते हैं ... लेकिन मेरी बात MSVC के व्यवहार किसी भी तरह से नहीं है "मानक"।
अंडरस्कोर_ड

7

एक साधारण जांच के लिए, आरटीटीआई एक सूचक तुलना के रूप में सस्ता हो सकता है। इनहेरिटेंस चेकिंग के लिए, यह strcmpविरासत के पेड़ के हर प्रकार के लिए उतना ही महंगा हो सकता है अगर आप dynamic_castएक कार्यान्वयन में ऊपर से नीचे तक नीचे की ओर हैं।

आप उपयोग न करके ओवरहेड को कम कर सकते हैं dynamic_castऔर इसके बजाय टाइप के माध्यम से और टाइपिड (...) == & टाइपिड (प्रकार) को स्पष्ट रूप से चेक कर सकते हैं । हालांकि यह जरूरी नहीं है कि .dll या अन्य गतिशील रूप से लोड किए गए कोड के लिए काम करें, यह उन चीजों के लिए काफी तेज हो सकता है जो सांख्यिकीय रूप से जुड़े हुए हैं।

हालांकि उस बिंदु पर यह एक स्विच स्टेटमेंट का उपयोग करने जैसा है, इसलिए आप वहां जाते हैं।


1
क्या आपके पास strcmp संस्करण के लिए कोई संदर्भ है? यह एक प्रकार की जाँच के लिए strcmp का उपयोग करने के लिए बेहद अक्षम और गलत लगता है।
JaredPar

एक खराब क्रियान्वयन में, जिसमें प्रति टाइप कई टाइप_इन्फो ऑब्जेक्ट हो सकते हैं, यह बूल टाइप_इन्फो :: ऑपरेटर == (कास्ट टाइप_इंफो & x) कॉन्स्ट "" के रूप में लागू कर सकता है! स्ट्रैम्प (नाम (), x.name ())
ग्रेग रोजर्स

3
MSVC के लिए डायनेमिक_कास्ट या टाइपिड () ऑपरेटर == के डिससैसम में कदम रखें और आप वहां स्ट्रैम्प मारेंगे। मैं इसके मामले को उस भयानक मामले के लिए मान रहा हूँ जहाँ आप एक अन्य .dll में संकलित प्रकार के खिलाफ तुलना कर रहे हैं। और यह मैंगल्ड नाम का उपयोग करता है, इसलिए कम से कम यह एक ही संकलक दिया गया है।
MSN

1
आप "टाइपिड (...) == टाइपिड (टाइप)" करने वाले हैं और पते की तुलना नहीं करते हैं
जोहान्स स्काउब -

1
मेरा कहना है कि आप जल्दी आउट के रूप में & टाइप (...) == & टाइपिड (ब्ला) कर सकते हैं और सुरक्षित रहेंगे। यह वास्तव में कुछ भी उपयोगी नहीं हो सकता है क्योंकि टाइपिड (...) स्टैक पर उत्पन्न हो सकता है, लेकिन यदि उनके पते समान हैं, तो उनके प्रकार समान हैं।
एमएसएन

6

यह हमेशा चीजों को मापने के लिए सबसे अच्छा है। निम्नलिखित कोड में, जी ++ के तहत, हाथ से कोडित प्रकार की पहचान का उपयोग आरटीटीआई की तुलना में लगभग तीन गुना तेज लगता है। मुझे यकीन है कि चरस के बजाय स्ट्रिंग्स का उपयोग करते हुए एक अधिक यथार्थवादी हाथ कोडेड क्रियान्वयन धीमा होगा, समय को करीब लाएगा।

#include <iostream>
using namespace std;

struct Base {
    virtual ~Base() {}
    virtual char Type() const = 0;
};

struct A : public Base {
    char Type() const {
        return 'A';
    }
};

struct B : public Base {;
    char Type() const {
        return 'B';
    }
};

int main() {
    Base * bp = new A;
    int n = 0;
    for ( int i = 0; i < 10000000; i++ ) {
#ifdef RTTI
        if ( A * a = dynamic_cast <A*> ( bp ) ) {
            n++;
        }
#else
        if ( bp->Type() == 'A' ) {
            A * a = static_cast <A*>(bp);
            n++;
        }
#endif
    }
    cout << n << endl;
}

1
इसे डायनामिक_कास्ट के साथ नहीं, बल्कि टाइपिड के साथ करने का प्रयास करें। यह प्रदर्शन को गति दे सकता है।
जोहान्स शहाब -

1
लेकिन डायनामिक_कास्ट का उपयोग करना अधिक यथार्थवादी है, कम से कम मेरे कोड को देख रहा है

2
यह एक अलग बात करता है: यह यह भी जाँचता है कि क्या b आपके ए == 'ए' से प्राप्त एक प्रकार की ओर इशारा करता है या नहीं, यह जाँचता है कि यह 'ए' की तरह इंगित करता है या नहीं। मुझे यह भी लगता है कि परीक्षण कुछ हद तक अनुचित है: संकलक आसानी से देख सकता है कि बी ए की तुलना में कुछ भी अलग नहीं कर सकता है, लेकिन मुझे लगता है कि यह यहां अनुकूलन नहीं करता है।
जोहान्स स्काउब -

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

हालाँकि इसे टाइप करने के लिए बदलने से यहाँ कोई फ़र्क नहीं पड़ता (फिर भी 0.016s)
जोहान्स

4

कुछ समय पहले मैंने एक 3ghz पावरपीसी के लिए MSVC और GCC के विशिष्ट मामलों में RTTI के लिए समय लागत को मापा था। मेरे द्वारा चलाए गए परीक्षणों में (एक गहरी श्रेणी के पेड़ के साथ एक काफी बड़ा C ++ ऐप), dynamic_cast<>0.8μs और 2μs के बीच प्रत्येक लागत, इस पर निर्भर करता है कि यह हिट हुआ या चूक गया।


2

तो, RTTI कितना महंगा है?

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

आपकी एकमात्र उम्मीद एक नमूना कार्यक्रम लिखने और यह देखने के लिए है कि आपका कंपाइलर क्या करता है (या कम से कम यह निर्धारित करता है कि एक मिलियन dynamic_castsया मिलियन typeidएस निष्पादित करने में कितना समय लगता है )।


1

RTTI सस्ता हो सकता है और इसके लिए स्ट्रैम्प की आवश्यकता नहीं होती है। कंपाइलर रिवर्स क्रम में वास्तविक पदानुक्रम करने के लिए परीक्षण को सीमित करता है। इसलिए यदि आपके पास एक क्लास C है जो कि क्लास B का एक बच्चा है जो क्लास A का बच्चा है, एक * * सेr एक C * ptr का एक बच्चा है। ptr का मतलब केवल एक पॉइंटर तुलना है और दो नहीं है (BTW, केवल vtp टेबल पॉइंटर है) तुलना में)। परीक्षण "यदि (vptr_of_obj == vptr_of_C) रिटर्न (C *)" जैसा है

एक और उदाहरण, यदि हम A * से B * के डायनेमिक_कास्ट करने का प्रयास करते हैं। उस स्थिति में, कंपाइलर दोनों मामलों की जाँच करेगा (एक बीजेपी होने के नाते, और बीजेपी होने के नाते) बारी में। इसे एकल परीक्षण (अधिकांश समय) के लिए भी सरल बनाया जा सकता है, क्योंकि वर्चुअल फंक्शन टेबल एक एकत्रीकरण के रूप में बनाया जाता है, इसलिए परीक्षण "if_of (vptr_of_obj, B) == vptr_of_B) के साथ फिर से शुरू होता है।"

ऑफ़सेट_ओफ़ = रिटर्न साइज़ोफ़ (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B: 0

की स्मृति लेआउट

vptr_of_C = [ vptr_of_A | vptr_of_new_methods_in_B | vptr_of_new_methods_in_C ]

संकलनकर्ता को संकलन समय पर इसे ऑप्टिमाइज़ करने का पता कैसे चलता है?

संकलन के समय, संकलक वस्तुओं की वर्तमान पदानुक्रम को जानता है, इसलिए यह विभिन्न प्रकार के पदानुक्रम डायनेमिक_कास्टिंग को संकलित करने से इनकार करता है। फिर इसे बस पदानुक्रम की गहराई को संभालना है, और इस तरह की गहराई से मिलान करने के लिए इनवर्टर की मात्रा को जोड़ना है।

उदाहरण के लिए, यह संकलन नहीं करता है:

void * something = [...]; 
// Compile time error: Can't convert from something to MyClass, no hierarchy relation
MyClass * c = dynamic_cast<MyClass*>(something);  

-5

RTTI "महंगा" हो सकता है क्योंकि आपने हर बार RTTI तुलना करने के बाद एक if-statement जोड़ा है। गहरी नेस्टेड पुनरावृत्तियों में, यह महंगा हो सकता है। एक लूप में निष्पादित होने वाली चीज में यह अनिवार्य रूप से मुफ्त है।

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

RTTI इसलिए भी महंगा है क्योंकि यह उपवर्ग पदानुक्रम (यदि एक भी हो) को अस्पष्ट कर सकता है। इसमें "ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग" से "ऑब्जेक्ट ओरिएंटेड" को हटाने का साइड-इफेक्ट हो सकता है।


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

1
@ क्रिस्टियन रोमियो: कृपया इन नए तथ्यों के साथ अपने प्रश्न को अपडेट करें। डायनेमिक_कास्ट C ++ में एक (कभी-कभी) आवश्यक बुराई है। RTTI प्रदर्शन के बारे में पूछने पर जब आप ऐसा करने के लिए मजबूर होते हैं तो इसका कोई मतलब नहीं है।
एस.लॉट

@ S.Lott: अपडेट किया गया। भ्रम के बारे में क्षमा करें।
क्रिस्टियान रोमो

1
मैंने अभी इस बारे में एक प्रयोग किया था - यह पता चला है कि आरटीटीआई ifआपके द्वारा शुरू किए गए रनटाइम प्रकार की जानकारी की जांच करने वाले विवरण की तुलना में काफी अधिक महंगा है ।
बोब्बोबो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.