RTTIの価格はどれくらいですか?


152

RTTIを使用することによるリソースへの影響があることは理解していますが、それはどれくらいの大きさですか?私が見たどこでも、「RTTIは高価です」とだけ言っていますが、実際には、メモリ、プロセッサ時間、または速度を保護するベンチマークや定量的なデータを提供するものはありません。

では、RTTIはどれほど高価なのでしょうか。RAMが4MBしかない組み込みシステムで使用する可能性があるため、すべてのビットがカウントされます。

編集:S.ロットの回答に従って、私が実際に行っていることを含めた方が良いでしょう。 私はクラスを使用して、さまざまな長さのデータを渡し、さまざまなアクションを実行できるため、仮想関数のみを使用してこれを行うのは難しいでしょう。数個dynamic_castのs を使用すると、さまざまな派生クラスをさまざまなレベルを通過させながら、まったく異なる動作をさせることで、この問題を解決できるようです。

私の理解から、dynamic_castRTTIを使用しているので、限られたシステムで使用するのがどれほど実現可能か疑問に思っていました。


1
あなたの編集に従ってください-私が自分でいくつかの動的キャストを実行していることに気付くと、Visitorパターンを使用すると物事が再びまっすぐになることに気づきます。それはあなたのために働くことができますか?
philsquared 2009

4
私はそれをこのように説明しますdynamic_cast-C ++で使い始めたばかりで、デバッガーでプログラムを「壊す」と、10回のうち9回は内部の動的キャスト関数の内部で壊されます。遅いです。
user541686 2012

3
ちなみにRTTI =「ランタイム型情報」。
Noumenon 2014年

回答:


115

コンパイラーに関係なく、実行する余裕がある場合は、常にランタイムを節約できます。

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

の代わりに

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

前者は、1つの比較のみを含みstd::type_infoます。後者は必然的に継承ツリーと比較をたどることを含みます。

それを過ぎると...誰もが言うように、リソースの使用は実装固有です。

送信者は設計上の理由からRTTIを避けるべきであるという他の皆のコメントに同意します。しかし、そこにあります RTTI(主にブーストの::いずれかの)を使用する理由は。そのことを念頭に置いて、一般的な実装での実際のリソース使用状況を知っておくと役立ちます。

私は最近、GCCのRTTIに関する一連の調査を行いました。

tl; dr:GCCのRTTIはごくわずかなスペースしか使用せずtypeid(a) == typeid(b)、非常に高速です。多くのプラットフォーム(Linux、BSD、およびおそらく組み込みプラットフォームですが、mingw32ではありません)。常に恵まれたプラットフォームを使用していることがわかっている場合、RTTIはほぼ無料です。

ざらざらした詳細:

GCCは特定の「ベンダー中立」のC ++ ABI [1]を使用することを好み、LinuxおよびBSDターゲット[2]には常にこのABIを使用します。このABIと弱いリンケージをサポートするプラットフォームのtypeid()場合、動的リンクの境界を越えても、各タイプの一貫した一意のオブジェクトを返します。あなたはテストすることができます&typeid(a) == &typeid(b)、または単にポータブルテストtypeid(a) == typeid(b)が実際にポインタを内部的に比較するだけであるという事実に依存することができます。

GCCの推奨ABIでは、クラスvtableは常に使用されない場合がありますが、タイプごとのRTTI構造へのポインターを保持します。したがって、typeid()呼び出し自体、他のvtableルックアップ(仮想メンバー関数の呼び出しと同じ)と同程度のコストしかかかりません。RTTIサポート、各オブジェクトに余分なスペースを使用しないでください

私が知ることができることから、GCCによって使用されるRTTI構造(これらはすべてのサブクラスですstd::type_info)は、名前を除いて、タイプごとに数バイトしか保持しません。でも、出力コードに名前が含まれているかどうかはわかりません-fno-rtti。どちらの方法でも、コンパイルされたバイナリのサイズの変更は、実行時のメモリ使用量の変更を反映する必要があります。

簡単な実験(Ubuntu 10.04 64ビットでGCC 4.4.3を使用)は、-fno-rtti実際に単純なテストプログラムのバイナリサイズを数百バイト増加させることを示しています。これは、-gとの組み合わせ全体で一貫して発生し-O3ます。サイズが大きくなる理由はわかりません。1つの可能性は、GCCのSTLコードがRTTIなしでは異なる動作をすることです(例外が機能しないため)。

[1] http://www.codesourcery.com/public/cxx-abi/abi.htmlに記載されているItanium C ++ ABIとして知られています。ABI仕様はi686 / x86_64を含む多くのアーキテクチャで機能しますが、名前はひどく混乱します。名前は元の開発アーキテクチャを示します。GCCの内部ソースとSTLコードのコメントでは、以前に使用されていた「古い」ものとは対照的に、Itaniumを「新しい」ABIと呼んでいます。さらに悪いことに、「新しい」/ Itanium ABIはを通じて利用可能なすべてのバージョンを指します-fabi-version。「古い」ABIはこのバージョン管理よりも前のバージョンです。GCCは、バージョン3.0でItanium / versioned / "new" ABIを採用しました。「古い」ABIは2.95以前で使用されました。

[2] std::type_infoプラットフォームごとのリソースリストオブジェクトの安定性が見つかりませんでした。私がアクセスできたコンパイラーには、以下を使用しましたecho "#include <typeinfo>" | gcc -E -dM -x c++ -c - | grep GXX_MERGED_TYPEINFO_NAMES。このマクロは、GCC 3.0以降、GCCのSTL でのoperator==for の動作を制御しますstd::type_info。私はmingw32-gccがWindows C ++ ABIに従っていることを発見しました。この場合、std::type_infoオブジェクトはDLL全体で型に対して一意ではありません。カバーの下でtypeid(a) == typeid(b)呼び出しstrcmpます。リンクするコードがないAVRのような単一プログラムの組み込みターゲットでは、std::type_infoオブジェクトは常に安定していると思います。


6
例外はRTTIなしで機能します。(あなたは投げることが許可されてintおり、その中にvtableはありません:))
ビリー・オニール

3
@Deduplicator:それでも、コンパイラでRTTIをオフにすると、問題なく動作します。あなたを失望させてすみません。
Billy ONeal、2014年

5
例外処理メカニズムは、いくつかのいくつかの基本的な要件を満たしている任意のタイプで機能する必要があります。RTTIを使用せずに、モジュールの境界を越えて任意の型の例外をスローおよびキャッチする方法を自由に提案できます。アップキャストとダウンキャストが必要であると考えてください。
Deduplicator 2014年

15
typeid(a)== typeid(b)は、B * ba = dynamic_cast <B *>(&a)と同じではありません。派生クラスツリーのランダムレベルとして多重継承を持つオブジェクトで試してみてください。typeid()== typeid()が正の値を生成しないことがわかります。dynamic_castは、継承ツリーを実際に検索する唯一の方法です。RTTIを無効にすることで節約の可能性について考えるのをやめて、それを使用するだけです。容量を超えている場合は、コードの膨張を最適化します。内部ループやその他のパフォーマンスクリティカルなコード内でdynamic_castを使用しないようにしてください。問題はありません。
mysticcoder

3
@mcoderが、記事で明示的に述べられている理由ですthe latter necessarily involves traversing an inheritance tree plus comparisons。@CoryB継承ツリー全体からのキャストをサポートする必要がない場合は、「余裕」で実行できます。たとえば、コレクションからX型のすべての項目を検索したいが、Xから派生した項目を検索したくない場合、使用するのは前者です。すべての派生インスタンスも検索する必要がある場合は、後者を使用する必要があります。
アイディアカピ2014

48

おそらく、これらの数値が役立つでしょう。

私はこれを使って簡単なテストをしていました:

  • GCC Clock()+ XCodeのプロファイラー。
  • 100,000,000回のループ反復。
  • 2 x 2.66 GHzデュアルコアインテルXeon。
  • 問題のクラスは、単一の基本クラスから派生しています。
  • typeid()。name()は "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()は、の2倍以上高速ですdyncamic_cast
  • 最近のマシンでは、両者の差は約1ナノ秒(100万分の1ミリ秒)です。

最適化あり(-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.

したがって、結論は次のようになります。

  • 最適化された単純なキャストケースの場合、typeid()はほぼx20高速ですdyncamic_cast

チャート

ここに画像の説明を入力してください

コード

コメントで要求されているように、コードは以下のとおりです(少し面倒ですが、動作します)。「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
ビリーONeal 2013

34
このベンチマークは、最適化によってまったく偽物です。typeidチェックはループ不変であり、ループの外に移動されます。それはまったく面白くありません、それはノーノーの基本的なベンチマークです。
モニカを

3
@クバ:ベンチマークは偽です。これが、最適化をオフにしてベンチマークを行う理由ではありません。それが、より良いベンチマークを書く理由です。
ビリーONeal 14

3
さらに、これは失敗です。「最適化された単純なキャストケースの場合、typeid()はdyncamic_castよりもx20近く高速です。」彼らは同じことをしません。dynamic_castが遅い理由があります。
mysticcoder

1
@KubaOber:合計+1。これはとても古典的です。そして、これが起こったことは、サイクル数を見れば明らかです。
v.oddou 2015

38

それは物事の規模に依存します。ほとんどの場合、これは2、3のチェックといくつかのポインター逆参照です。ほとんどの実装では、仮想関数を持つすべてのオブジェクトの上部に、そのクラスの仮想関数のすべての実装へのポインターのリストを保持するvtableへのポインターがあります。ほとんどの実装ではこれを使用して、クラスのtype_info構造体への別のポインターを格納すると思います。

たとえば、疑似c ++の場合:

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に対する本当の議論は、新しい派生クラスを追加するたびにコードをどこでも変更しなければならないことの保守性がないことです。どこでもswitchステートメントの代わりに、それらを仮想関数に分解します。これにより、クラス間で異なるすべてのコードがクラス自体に移動するため、新しい派生がすべての仮想関数をオーバーライドして、完全に機能するクラスになる必要があります。誰かがクラスのタイプをチェックして何か違うことをするたびに大きなコードベースを探し回らなければならなかった場合、そのプログラミングスタイルから離れることがすぐにわかります。

コンパイラーでRTTIを完全にオフにできる場合、最終的に得られるコードサイズの節約は、RAMスペースが非常に小さいため、大幅なものになる可能性があります。コンパイラーは、仮想関数を持つすべての単一クラスのtype_info構造を生成する必要があります。RTTIをオフにする場合、これらすべての構造を実行可能イメージに含める必要はありません。


4
なぜRTTIの使用が設計上の悪い決定と見なされるのかを実際に説明するための+1。
アグアザレス2013年

6
この答えは、C ++の能力についての低レベルの理解です。「一般的に」と「ほとんどの実装では」が自由に使用されているということは、言語機能の使い方をよく考えていないということです。仮想機能とRTTIの再実装は答えではありません。RTTIが答えです。オブジェクトが特定のタイプかどうかを知りたい場合もあります。それがそこにある理由です!したがって、いくつかのtype_info構造体で数KBのRAMを失います。ジー...
mysticcoder

16

まあ、プロファイラーは嘘をつかない。

私は18-20タイプの非常に安定した階層を持っているため、あまり変化しないので、単純なenum'dメンバーを使用するだけで問題が解決され、RTTIの「高」コストを回避できるのではないかと思いました。RTTIが実際にifそれが紹介する文よりも高価であるかどうか、私は懐疑的でした。ああ、そうですか。

RTTI 高価であり、同等のステートメントやC ++のプリミティブ変数の単純なステートメントよりもはるかに高価であることがわかります。したがって、S.Lottの答えは完全に正しいわけではありません。RTTIに追加のコストがかかります。それ、単に声明が混在しているためではありません。これは、RTTIが非常に高価であるためです。ifswitchif

このテストはApple LLVM 5.0コンパイラで行われ、ストックの最適化がオンになっています(デフォルトのリリースモード設定)。

したがって、私は以下の2つの関数を使用します。各関数は、1)RTTIまたは2)単純なスイッチを介してオブジェクトの具体的なタイプを計算します。それは50,000,000回そうします。さらに面倒なく、50,000,000回の実行の相対的な実行時間を示します。

ここに画像の説明を入力してください

そうです、実行時間の94%dynamicCastsかかりました。一方でブロックのみを取った3.3%regularSwitch

要するにenum、以下のように 'd型をフックするエネルギーを確保できるのであれば、RTTIを実行する必要がありパフォーマンスが最も重要な場合は、おそらくそれをお勧めします。メンバーの設定は1回のみです(必ずすべてのコンストラクターで取得してください)で確実に行われるようにします)、その後は絶対に記述しないでください。

そうは言っても、これを行ってもOOPプラクティスが台無しになることはありません。これは、タイプ情報が単に利用できず、RTTIを使い慣れている場合にのみ使用することを意図しています。

#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倍速くなる可能性があります。これは、typeid()の実装に依存して特定の方法で機能し、内部のcharポインターを比較するだけです。これは、以下と同等の場合もあります。

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

あなたはできますが、非常に高速型が一致した場合になりますどの安全にハイブリッドを使用して、比類のないタイプのための最悪のケースになります。

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

これを最適化する必要があるかどうかを理解するには、パケットの処理にかかる時間と比較して、新しいパケットの取得に費やす時間を確認する必要があります。ほとんどの場合、文字列の比較はおそらく大きなオーバーヘッドにはなりません。(クラスまたは名前空間によって異なります::クラス名の長さ)

これを最適化する最も安全な方法は、Baseクラスの一部として独自のtypeidをint(またはenum Type:int)として実装し、それを使用してクラスのタイプを決定してから、static_cast <>またはreinterpret_cast <を使用することです。 >

私にとっての違いは、最適化されていないMS VS 2005 C ++ SP1で約15倍です。


2
「標準のRTTIは、基になる文字列比較の実行に依存しているため、費用がかかります」-いいえ、これについての「標準」はありません。これは、実装のtypeid::operator動作方法です。たとえば、サポートされているプラ​​ットフォームのGCCは、char *sの比較をすでに使用していますが、強制することはありません-gcc.gnu.org/onlinedocs/gcc-4.6.3/libstdc++/api/…。確かに、あなたの方法では、MSVCの動作がプラットフォームのデフォルトよりもはるかに優れているので、高評価です。ポインタをネイティブで使用する「いくつかのターゲット」が何であるかはわかりません... "標準"。
underscore_d

7

単純なチェックの場合、RTTIはポインター比較と同じくらい安価です。継承チェックのstrcmp場合dynamic_cast、ある実装で上から下に- 継承ツリーのすべての型の場合と同じくらい高価になる可能性があります。

dynamic_cast&typeid(...)==&typeid(type)を使用して明示的にタイプをチェックすることにより、オーバーヘッドを削減することもできます。これは必ずしも.dllやその他の動的に読み込まれるコードでは機能しませんが、静的にリンクされているものでは非常に高速です。

その時点では、switchステートメントを使用するようなものですが、そこに行きます。


1
strcmpバージョンのリファレンスはありますか?型チェックにstrcmpを使用することは、非常に非効率で不正確なようです。
JaredPar 2009

タイプごとに複数のtype_infoオブジェクトを持つ可能性のある貧弱な実装では、bool type_info :: operator ==(const type_info&x)constを "!strcmp(name()、x.name())"として実装できます
Greg Rogers

3
MSVCのdynamic_castまたはtypeid()。operator ==の逆アセンブリにステップインすると、そこでstrcmpがヒットします。私は、別の.dllでコンパイルされたタイプと比較している恐ろしいケースのためにそこにあると思います。そして、それはマングルされた名前を使用するので、少なくとも同じコンパイラーが与えられればそれは正しいです。
MSN、

1
「typeid(...)== typeid(type)」を実行し、アドレスを比較しないことになっています
Johannes Schaub-litb

1
私のポイントは、&typeid(...)==&typeid(blah)を早い段階で実行でき、安全であることです。typeid(...)がスタックで生成される可能性があるため、実際には何も役に立たない可能性がありますが、それらのアドレスが等しい場合、それらの型は同じです。
MSN、

6

物事を測定することは常に最善です。次のコードでは、g ++では、手動でコード化された型識別を使用すると、RTTIの約3倍高速になるようです。文字ではなく文字列を使用した、より現実的な手でコード化された実装は遅くなり、タイミングが近づくと思います。

#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ではなくtypeidを使用してください。パフォーマンスが向上する可能性があります。
ヨハネスシャウブ-litb 2009

1
ただし、少なくとも私のコードを見ると、dynamic_castを使用する方がより現実的です

2
それは別のことをします:bpがAから派生した型を指しているかどうかもチェックします。あなたの== 'A'は、それが 'A'を正確に指しているかどうかをチェックします。また、テストは多少不公平だと思います。コンパイラーはbpがA以外のポイントをポイントできないことを簡単に確認できますが、ここでは最適化しないと思います。
ヨハネスショーブ-litb

とにかく、私はあなたのコードをテストしました。RTTIの場合は「0.016s」、仮想関数呼び出しの場合は「0.044s」になります。(-O2を使用)
Johannes Schaub-litb

typeidを使用するように変更しても、ここでは何の違いもありません(まだ0.016秒)
Johannes Schaub-litb

4

少し前に、3 GHzのPowerPCのMSVCおよびGCCの特定のケースでRTTIの時間コストを測定しました。私が実行したテスト(クラスツリーが深い、かなり大きなC ++アプリ)では、dynamic_cast<>ヒットしたか失敗したかに応じて、それぞれ0.8μsから2μsのコストがかかりました。


2

では、RTTIはどれほど高価なのでしょうか。

それは、使用しているコンパイラに完全に依存します。文字列比較を使用するものもあれば、実際のアルゴリズムを使用するものもあります。

あなたの唯一の望みは、サンプルプログラムを作成してコンパイラの動作を確認することです(または、少なくとも100万dynamic_castsまたは100万typeid秒を実行するのにかかる時間を決定すること)。


1

RTTIは安価である必要があり、strcmpを必ずしも必要としません。コンパイラーは、実際の階層を逆の順序で実行するようにテストを制限します。したがって、クラスAの子であるクラスBの子であるクラスCがある場合、A * ptrからC * ptrへのdynamic_castは、2つではなく1つだけのポインター比較を意味します(BTW、vptrテーブルポインターのみが比較)。テストは「if(vptr_of_obj == vptr_of_C)return(C *)obj」のようなものです

別の例として、A *からB *へのdynamic_castを実行するとします。その場合、コンパイラーは両方のケース(objがC、objがB)を順番にチェックします。仮想関数テーブルは集計として作成されるため、これは単一のテストに簡略化することもできます(ほとんどの場合)。したがって、テストは「if(offset_of(vptr_of_obj、B)== vptr_of_B)」に再開されます。

offset_of = return sizeof(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 ]

コンパイラは、コンパイル時にこれを最適化することをどのように知っていますか?

コンパイル時に、コンパイラーはオブジェクトの現在の階層を認識しているため、異なるタイプの階層dynamic_castingのコンパイルを拒否します。次に、階層の深さを処理し、その深さに一致するようにテストの逆数を追加するだけです。

たとえば、これはコンパイルされません。

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

-5

RTTI比較を行うたびにifステートメントを追加するため、RTTIは「高価」になる可能性があります。深くネストされた反復では、これは高価になる可能性があります。ループで実行されないものでは、本質的に無料です。

適切なポリモーフィックデザインを使用して、ifステートメントを削除することを選択します。深くネストされたループでは、これはパフォーマンスに不可欠です。それ以外の場合は、あまり問題ではありません。

また、RTTIは、サブクラス階層がある場合でもそれを覆い隠す可能性があるため、コストがかかります。「オブジェクト指向プログラミング」から「オブジェクト指向」を削除するという副作用があります。


2
必ずしもそうとは限りません。各サブタイプには異なる(可変サイズの)データを適用する必要があるため、ダウンキャストする必要があるため、dynamic_castを介して間接的に使用し、階層を維持します。
クリスティアンロモ

1
@CristiánRomo:これらの新しい事実で質問を更新してください。dynamic_castは(時々)C ++で必要な悪です。強制的にRTTIのパフォーマンスについて尋ねることは、あまり意味がありません。
S.Lott

@ S.Lott:更新されました。混乱については申し訳ありません。
クリスティアンロモ

1
私はこれについて今実験しまし - if実行時の型情報をこの方法でチェックするときにRTTIが導入するステートメントよりもはるかに高価であることがわかりました。
bobobobo 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.