RTTI ne kadar pahalıdır?


152

RTTI kullanımından bir kaynak olduğunu anlıyorum, ama ne kadar büyük? Baktığım her yer "RTTI pahalı" diyor, ancak hiçbiri aslında belleği, işlemci süresini veya hızı düzenleyen herhangi bir kıyaslama veya nicel veri vermiyor.

Peki, RTTI ne kadar pahalı? Sadece 4MB RAM'e sahip gömülü bir sistemde kullanabilirim, bu yüzden her bit önemlidir.

Düzenleme: S. Lott'un cevabına göre , aslında ne yaptığımı eklersem daha iyi olurdu. Farklı uzunluklarda veri aktarmak için bir sınıf kullanıyorum ve farklı eylemler gerçekleştirebilir , bu yüzden bunu sadece sanal işlevleri kullanarak yapmak zor olurdu. Birkaç kullanarakdynamic_cast s , farklı türetilmiş sınıfların farklı seviyelerden geçmesine izin vererek, ancak yine de tamamen farklı hareket etmelerine izin vererek bu sorunu çözebileceği görülmektedir.

Anladığım kadarıyla dynamic_castRTTI kullanıyor, bu yüzden sınırlı bir sistemde kullanmanın ne kadar mümkün olacağını merak ediyordum.


1
Düzenlemenizden sonra - çoğu zaman kendimi birkaç dinamik döküm yaparken bulduğumda, Ziyaretçi desenini kullanmanın bir şeyleri tekrar düzelttiğini fark ediyorum. Bu senin için işe yarayabilir mi?
philsquared

4
Bu şekilde koyacağım - dynamic_castC ++ 'da kullanmaya başladım ve şimdi, programı hata ayıklayıcı ile "kırdığımda" 10'dan 9'u, dahili dinamik döküm işlevinin içinde kırılıyor. Çok yavaş.
user541686

3
RTTI = "çalışma zamanı türü bilgileri" bu arada.
Noumenon

Yanıtlar:


115

Derleyiciden bağımsız olarak, bunu karşılayabiliyorsanız her zaman çalışma zamanında tasarruf edebilirsiniz

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

onun yerine

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

İlki sadece bir karşılaştırmayı içerir std::type_info; ikincisi zorunlu olarak bir miras ağacının ve karşılaştırmaları geçmeyi içerir.

Geçmişte ... herkesin dediği gibi, kaynak kullanımı uygulamaya özeldir.

Gönderenin tasarım nedenleriyle RTTI'dan kaçınması gerektiğini herkesin yorumlarına katılıyorum. Bununla birlikte, RTTI kullanmak için iyi nedenler vardır (esas olarak boost :: any nedeniyle). Bunu akılda tutarak, ortak uygulamalarda gerçek kaynak kullanımını bilmek yararlıdır.

Geçenlerde GCC'de RTTI ile ilgili bir sürü araştırma yaptım.

tl; dr: GCC'deki RTTI typeid(a) == typeid(b), birçok platformda (Linux, BSD ve belki de gömülü platformlar, ancak mingw32 değil) ihmal edilebilir alan kullanır ve çok hızlıdır. Her zaman kutsanmış bir platformda olacağınızı biliyorsanız, RTTI ücretsizdir.

Cesur ayrıntılar:

GCC belirli bir "satıcıdan bağımsız" C ++ ABI [1] kullanmayı tercih eder ve bu ABI'yi her zaman Linux ve BSD hedefleri için kullanır [2]. Bu ABI'yi ve aynı zamanda zayıf bağlantıyı destekleyen platformlar typeid()için, dinamik bağlantı sınırları dahilinde bile her tür için tutarlı ve benzersiz bir nesne döndürür. Test edebilir &typeid(a) == &typeid(b)veya taşınabilir testin typeid(a) == typeid(b)aslında sadece bir işaretçiyi dahili olarak karşılaştırdığına güvenebilirsiniz .

GCC'nin tercih edilen ABI'sinde, bir sınıf vtable her zaman tür başına RTTI yapısına bir işaretçi tutar, ancak kullanılmayabilir. Bir Böylece typeid()çağrı kendisi olmalıdır yalnızca başka vtable arama (sanal üye işlevini çağırarak aynı) kadar mal ve RTTI destek olmamalıdır her nesne için herhangi bir ekstra boşluk kullanın.

Yapabileceğimden, GCC tarafından kullanılan RTTI yapıları (bunların tüm alt sınıfları std::type_info), adın yanı sıra, her tür için sadece birkaç bayt tutar. İsimlerin çıktı kodunda bile bulunup bulunmadığı net değil -fno-rtti. Her iki durumda da, derlenmiş ikilinin boyutundaki değişiklik, çalışma zamanı bellek kullanımındaki değişikliği yansıtmalıdır.

Hızlı bir deneme (Ubuntu 10.04 64 bit üzerinde GCC 4.4.3 kullanarak), basit bir test programının ikili boyutunu -fno-rttiaslında birkaç yüz bayt artırdığını göstermektedir . Bu sürekli -gve ve kombinasyonları arasında gerçekleşir -O3. Boyutun neden artacağından emin değilim; bir olasılık, GCC'nin STL kodunun RTTI olmadan farklı davranmasıdır (istisnalar çalışmadığından).

[1] http://www.codesourcery.com/public/cxx-abi/abi.html adresinde belgelenen Itanium C ++ ABI olarak bilinir . İsimler korkunç derecede kafa karıştırıcı: isim orijinal geliştirme mimarisine atıfta bulunsa da, ABI belirtimi i686 / x86_64 dahil birçok mimaride çalışıyor. GCC'nin iç kaynağındaki ve STL kodundaki yorumlar, Itanium'a daha önce kullandıkları "eski" ifadenin aksine "yeni" ABI olarak atıfta bulunur. Daha da kötüsü, "yeni" / Itanium ABI, mevcut tüm sürümleri ifade eder -fabi-version; "eski" ABI bu sürümün önüne geçti. GCC, 3.0 sürümünde Itanium / versioned / "new" ABI'yi kabul etti; "eski" ABI 2.95 ve önceki sürümlerde kullanıldı.

[2] std::type_infoPlatforma göre herhangi bir kaynak listeleme nesnesi kararlılığı bulamadım . Derleyici için ben aşağıdakileri kullanılan erişimi vardı: echo "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES. Bu makro kontroller davranışı operator==için std::type_infoGCC 3.0 itibariyle GCC en STL. Mingw32-gcc, std::type_infonesnelerin DLL'lerde bir tür için benzersiz olmadığı Windows C ++ ABI, itaat ettiğini buldum ; örtü altında typeid(a) == typeid(b)çağırır strcmp. Bağlanacak bir kodun olmadığı AVR gibi tek programlı gömülü hedeflerde, std::type_infonesnelerin her zaman kararlı olduğunu tahmin ediyorum .


6
İstisnalar RTTI olmadan çalışır. (Bir atış yapmanıza izin verilir intve hiçbir
oyu

3
@Deduplicator: Yine de, derleyicimdeki RTTI'yi kapattığımda, gayet iyi çalışıyorlar. Sizi hayal kırıklığına uğrattığım için üzgünüm.
Billy ONeal

5
İstisna işleme mekanizması, birkaç temel gereksinimi karşılayan herhangi bir türle çalışabilmelidir. RTTI'sız modül sınırları boyunca rastgele tip atma ve yakalama istisnalarının nasıl ele alınacağını önermekte özgürsünüz . Lütfen yukarı ve aşağı dökümün gerekli olduğunu düşünün.
Tekilleştirici

15
typeid (a) == typeid (b), B * ba = dynamic_cast <B *> (& a) ile aynı DEĞİLDİR. Türetilmiş sınıf ağacında rastgele bir seviye olarak birden çok mirasa sahip nesneler üzerinde deneyin ve typeid () == typeid () 'nin pozitif vermediğini göreceksiniz. dynamic_cast, miras ağacında gerçek arama yapmanın tek yoludur. RTTI'yı devre dışı bırakarak potansiyel tasarrufları düşünmeyi bırakın ve sadece kullanın. Kapasiteniz yüksekse kod şişkinliğinizi optimize edin. İç döngülerde veya başka bir performans açısından kritik kodda dynamic_cast kullanmaktan kaçınmaya çalışın.
mysticcoder

3
@mcoder Bu yüzden makale bunu açıkça belirtiyor the latter necessarily involves traversing an inheritance tree plus comparisons. @CoryB Tüm miras ağacından yayın yapmayı desteklemeniz gerekmediğinde bunu "karşılayabilirsiniz". Örneğin, bir koleksiyondaki X türünün tüm öğelerini bulmak istiyorsanız, ancak X'den türeyenleri bulmak istemiyorsanız, ilk önce kullanmanız gereken şeydir. Tüm türetilmiş örnekleri de bulmanız gerekiyorsa, ikincisini kullanmanız gerekir.
Aidiakapi

48

Belki bu rakamlar yardımcı olacaktır.

Bunu kullanarak hızlı bir test yapıyordum:

  • GCC Clock () + XCode Kullanıcı Profili.
  • 100.000.000 döngü yinelemesi.
  • 2 x 2,66 GHz Çift Çekirdekli Intel Xeon.
  • Söz konusu sınıf tek bir temel sınıftan türetilir.
  • typeid (). name () "N12fastdelegate13FastDelegate1IivEE" döndürür

5 vaka test edildi:

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 )
   }

Zaten sahip olduğum birine benzer olup olmadığını kontrol etmeden önce bu tür bir nesne oluşturmak için gerekli olduğu gibi 5, sadece benim gerçek kod.

Optimizasyon Olmadan

Hangi sonuçlar için (birkaç koşu ortalaması aldım):

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.

Sonuç şu olacaktır:

  • Optimizasyon olmadan basit oyuncular için typeid()bundan iki kat daha hızlıdır dyncamic_cast.
  • Modern bir makinede ikisi arasındaki fark yaklaşık 1 nanosaniyedir (milisaniyenin milyonda biri).

Optimizasyon ile (-Os)

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.

Sonuç şu olacaktır:

  • Optimizasyonlu basit oyuncular için typeid(), neredeyse x20 daha hızlıdır dyncamic_cast.

Grafik

resim açıklamasını buraya girin

Kod

Yorumlarda istendiği gibi, kod aşağıdadır (biraz dağınık, ama çalışıyor). 'FastDelegate.h' adresine buradan ulaşabilirsiniz .

#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
Tabii ki, dinamik döküm daha geneldir - öğe daha türetilmişse çalışır. Örneğin class a {}; class b : public a {}; class c : public b {};, hedefin bir örneği olduğu czaman, sınıf bile test dynamic_castederken typeidçözüm ile iyi çalışmaz . Yine de makul, +1
Billy ONeal

34
Bu kıyaslama tamamen optimizasyonlarla düzmece : tip kimliği kontrolü döngü değişmezdir ve döngüden çıkarılır. Hiç ilginç değil, temel bir kıyaslama hayır.
Monica

3
@Kuba: Öyleyse temel ölçüt sahte. Optimizasyon kapalıyken kıyaslama yapmak için bir neden değil; bu daha iyi kriterler yazmak için bir neden.
Billy ONeal

3
yine, bu bir başarısızlık. "Optimizasyonlu basit döküm durumlarda, typeid () dyncamic_cast'ten neredeyse x20 daha hızlıdır." onlar da aynı şeyi yapmazlar. Dynamic_cast'in daha yavaş olmasının bir nedeni var.
mysticcoder

1
@KubaOber: toplam +1. bu çok klasik. ve bunun gerçekleştiği döngü sayısı açısından açık olmalıdır.
v.oddou

38

Şeylerin ölçeğine bağlıdır. Çoğunlukla sadece birkaç kontrol ve birkaç işaretçi dereferences. Çoğu uygulamada, sanal işlevleri olan her nesnenin üstünde, o sınıftaki sanal işlevin tüm uygulamalarına bir işaretçi listesi tutan bir vtable'a işaretçi vardır. Çoğu uygulama bu sınıf için type_info yapısına başka bir işaretçi saklamak için kullanacağını tahmin ediyorum.

Örneğin, sözde-c ++ ile:

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);       
}

Genel olarak RTTI'ya karşı asıl argüman, her yeni türetilmiş sınıf eklediğinizde kodu her yerde değiştirmek zorunda kalmamaktır. Her yerde switch deyimleri yerine, bu ifadeleri sanal işlevlere katın. Bu, sınıflar arasında farklı olan tüm kodları sınıfların içine taşır, böylece yeni bir türetmenin tam olarak çalışan bir sınıf haline gelmesi için tüm sanal işlevleri geçersiz kılması gerekir. Birisi bir sınıfın türünü her kontrol ettiğinde ve farklı bir şey yaptığında, büyük bir kod tabanından avlanmak zorunda kaldıysanız, bu programlama stilinden uzak durmayı hızlı bir şekilde öğreneceksiniz.

Derleyiciniz RTTI'yi tamamen kapatmanıza izin veriyorsa, sonuçta ortaya çıkan nihai kod boyutu tasarrufu, bu kadar küçük bir RAM alanı ile önemli olabilir. Derleyicinin sanal işlevli her sınıf için bir type_info yapısı oluşturması gerekir. RTTI'yı kapatırsanız, tüm bu yapıların yürütülebilir görüntüye dahil edilmesi gerekmez.


4
RTTI kullanımının neden kötü bir tasarım kararı olarak kabul edildiğini açıklayan +1, bu benim için daha önce net değildi.
aguazales

6
Bu cevap C ++ 'ın gücünün düşük düzeyde anlaşılmasıdır. "Genel olarak" ve "Çoğu uygulamada" liberal olarak kullanılır, dillerin özelliklerini iyi kullanmayı düşünmediğiniz anlamına gelir. Sanal fonksiyonlar ve RTTI'nın yeniden uygulanması cevap değildir. RTTI cevaptır. Bazen bir nesnenin belirli bir tür olup olmadığını bilmek istersiniz. Bu yüzden orada! Böylece bazı type_info yapılarına birkaç KB RAM kaybedersiniz. Gee ...
mysticcoder

16

Profiler asla yalan söylemez.

Çok değişmeyen 18-20 tür oldukça istikrarlı bir hiyerarşim olduğu için, sadece basit bir numaralandırılmış üye kullanmanın hile yapıp RTTI'nın "yüksek" maliyetinden kaçınıp kaçmayacağını merak ettim . RTTI aslında ifsunduğu açıklamadan daha pahalı olsaydı şüpheliydim . Oğlum oh oğlum, öyle mi?

RTTI'nin pahalı, eşdeğer bir ifadeden çok daha pahalı veya C ++ 'da ilkel bir değişken üzerinde basit olduğu ortaya çıkıyor . S.Lott cevabı tamamen doğru değildir Yani, orada olduğu RTTI için ekstra maliyet ve bu kadar değil sadece nedeniyle bir olan ifadeyi karışımı. Bunun nedeni RTTI'nin çok pahalı olmasıdır.ifswitchif

Bu test, stok optimizasyonları açıkken (varsayılan yayın modu ayarları) Apple LLVM 5.0 derleyicisinde yapıldı.

Yani, her biri 1) RTTI veya 2) basit bir anahtar ile bir nesnenin somut tipini anlayan 2'nin altında fonksiyonum var. Bunu 50.000.000 kez yapıyor. Daha fazla uzatmadan, size 50.000.000 koşu için göreceli çalışma zamanlarını sunuyorum.

resim açıklamasını buraya girin

Bu doğru, çalışma süresinin % 94'ünüdynamicCasts aldı . Birlikte blok sadece aldı 3.3% .regularSwitch

Uzun lafın kısası: enumAşağıda yaptığım gibi bir 'd tipini bağlamak için enerjiyi karşılayabiliyorsanız, RTTI yapmanız gerekiyorsa ve performans çok önemliyse, muhtemelen tavsiye ederim . Üyenin yalnızca bir kez ayarlanması gerekir ( tüm kuruculardan aldığınızdan emin olun ) ve daha sonra asla yazmadığınızdan emin olun.

Bununla birlikte, bunu yapmak OOP uygulamalarınızı bertaraf etmemelidir .. sadece tip bilgisi mevcut olmadığında ve kendinizi RTTI kullanarak köşeli bulduğunuzda kullanılmalıdır.

#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

Standart yolu:

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

Standart RTTI pahalıdır, çünkü altta yatan bir dize karşılaştırması yapmaya dayanır ve bu nedenle RTTI hızı sınıf adı uzunluğuna bağlı olarak değişebilir.

Dize karşılaştırmalarının kullanılmasının nedeni, kitaplık / DLL sınırları boyunca tutarlı bir şekilde çalışmasını sağlamaktır. Uygulamanızı statik olarak oluşturuyorsanız ve / veya belirli derleyiciler kullanıyorsanız muhtemelen şunları kullanabilirsiniz:

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

Çalışması garanti edilmez (asla yanlış pozitif vermez, ancak yanlış negatif verebilir) ancak 15 kata kadar daha hızlı olabilir. Bu, belirli bir şekilde çalışmak için typeid () uygulamasına dayanır ve yaptığınız tek şey bir iç karakter işaretçisini karşılaştırmaktır. Bu bazen aşağıdakilere de eşdeğerdir:

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

Sen olabilir ancak çok hızlı türleri eşleşirse olacak güvenle bir melez kullanın ve eşsiz türleri için en kötü durum olacaktır:

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

Bunu optimize etmeniz gerekip gerekmediğini anlamak için, paketi işlemek için geçen süreye kıyasla, yeni bir paket almak için ne kadar zaman harcadığınızı görmeniz gerekir. Çoğu durumda, bir dize karşılaştırması büyük bir ek yük olmayacaktır. (sınıfınıza veya ad alanınıza bağlı olarak :: sınıf adı uzunluğu)

Bunu optimize etmenin en güvenli yolu, Base sınıfınızın bir parçası olarak kendi tip kimliğinizi int (veya enum Type: int) olarak uygulamak ve bunu sınıfın türünü belirlemek için kullanmaktır ve daha sonra static_cast <> veya reinterpret_cast < >

Benim için fark, optimize edilmemiş MS VS 2005 C ++ SP1'de yaklaşık 15 kat.


2
"Standart RTTI pahalıdır çünkü altta yatan bir dize karşılaştırması yapmaya dayanır" - hayır, bununla ilgili "Standart" hiçbir şey yok; bu sadece nasıl uygulanması en typeid::operatorun eserleri . Örneğin, desteklenen bir platformdaki GCC, s'yichar * zorlamaksızın zaten karşılaştırmaları kullanıyor - gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/… . Elbette, yolunuz MSVC'yi platformunuzda varsayılandan çok daha iyi davranıyor, bu yüzden kudoslar ve yerel olarak işaretçiler kullanan "bazı hedeflerin" ne olduğunu bilmiyorum ... ama benim açımdan MSVC'nin davranışı hiçbir şekilde değil "Standart".
underscore_d

7

Basit bir kontrol için, RTTI bir işaretçi karşılaştırması kadar ucuz olabilir. Kalıtım kontrolü için, orada bir uygulamada yukarıdan aşağıya doğru iniş strcmpyapıyorsanız, kalıtım ağacındaki her tür için pahalı olabilir dynamic_cast.

Ayrıca dynamic_cast, tipi kullanmadan ve bunun yerine & typeid (...) == & typeid (type) aracılığıyla türü açıkça kontrol ederek ek yükü azaltabilirsiniz . Bu mutlaka .dlls veya dinamik olarak yüklenen diğer kodlar için çalışmaz, ancak statik olarak bağlı şeyler için oldukça hızlı olabilir.

Bu noktada bir switch deyimi kullanmak gibi olsa da, işte böyle.


1
Strcmp sürümü için herhangi bir başvurunuz var mı? Bir tür kontrol için strcmp kullanmak son derece verimsiz ve yanlış görünüyor.
JaredPar

Tür başına birden fazla type_info nesnesi olabilen zayıf bir uygulamada, bool type_info :: operator == (const type_info & x) const "! Strcmp (name (), x.name ())" olarak uygulanabilir
Greg Rogers

3
MSVC için dynamic_cast veya typeid () operatörünün == sökülmesine adım atın ve orada bir strcmp'ye çarpacaksınız. Onun orada başka bir .dll derlenmiş bir tür karşılaştırarak korkunç durum için varsayalım. Ve karışık adı kullanır, bu yüzden aynı derleyici göz önüne alındığında en azından doğrudur.
MSN

1
"typeid (...) == typeid (type)" yapmanız ve adresi karşılaştırmamanız gerekiyor
Johannes Schaub - litb

1
Demek istediğim & typeid (...) == & typeid (blah) erken çıkış yapabilir ve güvende olacaksınız. Yığın üzerinde typeid (...) oluşturulabileceğinden aslında yararlı bir şey yapmayabilir, ancak adresleri eşitse, türleri eşittir.
MSN

6

Bir şeyi ölçmek her zaman en iyisidir. Aşağıdaki kodda, g ++ altında, elle kodlanan tip tanımlamasının kullanımı RTTI'dan yaklaşık üç kat daha hızlı görünmektedir. Karakterler yerine dizeleri kullanarak daha gerçekçi bir el kodlu uygulamanın daha yavaş olacağından, zamanlamaları birbirine yakınlaştırdığından eminim.

#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
dynamic_cast ile değil, typeid ile yapmayı deneyin. performansı hızlandırabilir.
Johannes Schaub - litb

1
ama dynamic_cast kullanmak daha gerçekçi, en azından koduma bakarken

2
farklı bir şey yapar: bp'nin A'dan türetilmiş bir türü gösterip göstermediğini de kontrol eder. == 'A', tam olarak bir 'A' yı gösterip göstermediğini kontrol eder. Ayrıca test biraz haksız olduğunu düşünüyorum: derleyici kolayca bp A farklı bir şey işaret edemez görebilirsiniz ama burada optimize etmez düşünüyorum.
Johannes Schaub - litb

Her neyse, kodunuzu test ettim. ve bana RTTI için "0.016s" ve sanal fonksiyon çağrıları için "0.044s" veriyor. (-O2 kullanarak)
Johannes Schaub - litb

ancak typeid kullanmak için değiştirmek burada bir fark yaratmaz (hala 0.016s)
Johannes Schaub - litb

4

Bir süre önce 3ghz PowerPC için belirli MSVC ve GCC vakalarında RTTI için zaman maliyetlerini ölçtüm. Testlerde koştum (derin bir sınıf ağacı olan oldukça büyük bir C ++ uygulaması), her biri dynamic_cast<>0.8μs ile 2μs arasında, vurduğuna veya kaçırdığına bağlı olarak maliyet.


2

Peki, RTTI ne kadar pahalı?

Bu tamamen kullandığınız derleyiciye bağlıdır. Bazılarının dize karşılaştırmaları kullandığını ve diğerlerinin gerçek algoritmalar kullandığını anlıyorum.

Tek umudunuz örnek bir program yazmak ve derleyicinizin ne yaptığını görmek (veya en azından bir milyon dynamic_castsveya bir milyon typeids süresinin ne kadar süreceğini belirlemek ).


1

RTTI ucuz olabilir ve mutlaka bir strcmp'ye ihtiyaç duymaz. Derleyici, testi gerçek hiyerarşiyi ters sırayla gerçekleştirecek şekilde sınırlar. Bu nedenle, A sınıfının alt öğesi olan B sınıfının alt öğesi olan bir C sınıfınız varsa, A * ptr'den C * ptr'ye dinamik_ yayın, iki değil yalnızca bir işaretçi karşılaştırması gerektirir (BTW, yalnızca vptr tablo işaretçisi karşılaştırıldığında). Test "if (vptr_of_obj == vptr_of_C) dönüş (C *) obj" ise

A * 'dan B *' ya dynamic_cast yapmaya çalışırsak başka bir örnek. Bu durumda, derleyici sırayla her iki durumu da (obj C olarak ve obj B olarak) kontrol eder. Bu, sanal işlev tablosu bir toplama olarak yapıldığı için tek bir testle (çoğu zaman) basitleştirilebilir, bu nedenle test, "if (offset_of (vptr_of_obj, B) == vptr_of_B)" ile

offset_of = dönüş sizeof (vptr_table)> = sizeof (vptr_of_B)? vptr_of_new_methods_in_B: 0

Bellek düzeni

vptr_of_C = [ vptr_of_A | vptr_of_new_methods_in_B | vptr_of_new_methods_in_C ]

Derleyici bunu derleme zamanında optimize etmeyi nasıl bilebilir?

Derleme zamanında, derleyici nesnelerin geçerli hiyerarşisini bilir, bu nedenle farklı dinamik_casting tür hiyerarşisini derlemeyi reddeder. Daha sonra sadece hiyerarşi derinliğini işlemeli ve bu derinliğe uyacak şekilde ters çevir testlerini eklemelidir.

Örneğin, bu derlenmez:

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

-5

RTTI "pahalı" olabilir, çünkü RTTI karşılaştırmasını her yaptığınızda bir if ifadesi eklediniz. Derin iç içe yinelemelerde bu pahalı olabilir. Asla bir döngüde idam edilmeyen bir şeyde aslında ücretsizdir.

Seçim, if ifadesini ortadan kaldırarak uygun polimorfik tasarımı kullanmaktır. Derin iç içe döngülerde, bu performans için önemlidir. Aksi takdirde çok da önemli değil.

RTTI da pahalıdır çünkü alt sınıf hiyerarşisini gizleyebilir (eğer varsa). "Nesne yönelimli" programın "nesne yönelimli programlama" dan kaldırılmasının yan etkisi olabilir.


2
Mutlaka değil - Ben dolaylı olarak dynamic_cast üzerinden kullanmak ve hiyerarşi yerinde tutmak, çünkü her alt tip farklı uygulanması gereken değişken (değişken boyutlu) veri, bu nedenle dinamik_cast çünkü mahzun gerekir.
Cristián Romo

1
@ Cristián Romo: Lütfen sorunuzu bu yeni gerçeklerle güncelleyin. dynamic_cast C ++ 'da (bazen) gerekli bir kötülüktür. Zorlandığınızda RTTI performansını sormak pek mantıklı değil.
S.Lott

@ S.Lott: Güncellendi. Karışıklık için üzgünüm.
Cristián Romo

1
Şu anda bu konuda bir deney yaptım - RTTI, ifçalışma zamanı türü bilgilerini bu şekilde kontrol ettiğinizde tanıttığınız ifadeden çok daha pahalı .
13'te bobobobo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.