web-dev-qa-db-ja.com

dynamic_castのパフォーマンス?

質問を読む前に:
この質問は、dynamic_castを使用することの有用性についてではありません。そのちょうどそのパフォーマンスについて。

最近、dynamic_castを多用するデザインを開発しました。
同僚と話し合うと、ほとんどの人がdynamic_castはパフォーマンスが悪いため使用しないでください(これらは異なる背景を持ち、場合によってはお互いを知らない同僚です) 。私は巨大な会社で働いています)

私は、この方法を信じるだけでなく、そのパフォーマンスをテストすることにしました。

次のコードが使用されました:

ptime firstValue( microsec_clock::local_time() );

ChildObject* castedObject = dynamic_cast<ChildObject*>(parentObject);

ptime secondValue( microsec_clock::local_time() );
time_duration diff = secondValue - firstValue;
std::cout << "Cast1 lasts:\t" << diff.fractional_seconds() << " microsec" << std::endl;

上記のコードでは、Linuxのboost::date_timeのメソッドを使用して、使用可能な値を取得しています。
1回の実行で3 dynamic_castを実行しました。それらを測定するためのコードは同じです。

1実行の結果は次のとおりです。
Cast1持続時間:74マイクロ秒
Cast2持続時間:2マイクロ秒
Cast3持続時間:1マイクロ秒

最初のキャストは常に74〜111マイクロ秒かかりましたが、同じ実行での次のキャストは1〜3マイクロ秒かかりました。

最後に私の質問:
dynamic_castのパフォーマンスは本当に悪いですか?
テスト結果によると、そうではありません。テストコードは正しいですか?
なぜ多くの開発者は、遅くなければ遅いと思いますか?

40
MOnsDaR

最初に、結果はタイマーの分解能に支配されるため、数回の反復よりもはるかに多くのパフォーマンスを測定する必要があります。たとえば、代表的な写真を作成するために、100万以上。また、この結果は、何かと比較しない限り、つまり動的キャストなしで同等のことを行わない限り意味がありません。

次に、同じポインターで複数の動的キャストを最適化することにより、コンパイラーが誤った結果を出さないようにする必要があります(ループを使用しますが、毎回異なる入力ポインターを使用します)。

オブジェクトのRTTI(ランタイムタイプ情報)テーブルにアクセスし、キャストが有効であることを確認する必要があるため、動的キャストは遅くなります。次に、適切に使用するために、返されたポインタがNULLかどうかをチェックするエラー処理コードを追加する必要があります。これにはすべてサイクルがかかります。

あなたはこれについて話したくなかったのを知っていますが、「dynamic_castが頻繁に使用されるデザイン」は、おそらく何か間違っていることを示しています...

52

同等の機能を比較しなければ、パフォーマンスは意味がありません。ほとんどの人は、同等の動作と比較しなければ、dynamic_castは遅いと言います。これについて彼らに声をかけなさい。別の言い方をすると:

「機能する」が要件ではない場合、私はあなたよりも速く失敗するコードを書くことができます。

Dynamic_castを実装するにはさまざまな方法があり、いくつかは他よりも高速です。 Stroustrupが primes to dynamic_cast の使用に関する論文を公開しました。残念ながら、コンパイラがキャストを実装する方法を制御することはまれですが、パフォーマンスが本当に重要な場合は、使用するコンパイラを制御できます。

ただし、を使用しないdynamic_castは常にを使用するより高速ですが、実際にdynamic_castが必要ない場合は、それを使用してください!動的ルックアップが必要な場合は、オーバーヘッドが発生するため、さまざまな戦略を比較できます。

30
Roger Pate

ここにいくつかのベンチマークがあります:
http://tinodidriksen.com/2010/04/14/cpp-dynamic-cast-performance/
http://www.nerdblog.com/2006/12/how-slow-is-dynamiccast.html

彼らによれば、dynamic_castはreinterpret_castよりも5〜30倍遅く、最良の代替案はreinterpret_castとほぼ同じように動作します。

最初の記事の結論を引用します。

  • dynamic_castは、基本タイプへのキャスト以外のすべての場合は低速です。その特定のキャストは最適化されています
  • 継承レベルはdynamic_castに大きな影響を与えます
  • メンバー変数+ reinterpret_castは、
    タイプを決定します;ただし、メンテナンスのオーバーヘッドがはるかに大きくなります。
    コーディング時

絶対値は、1回のキャストで100 ns程度です。 74ミリ秒のような値は現実に近いようには見えません。

21
VladV

申し訳ありませんが、キャストが遅いかどうかを判断するためのテストは事実上役に立ちません。マイクロセカンドの分解能は、十分に良いとは言えません。最悪のシナリオでも、典型的なPCで100クロックティック以上、または50ナノ秒未満を要しない操作について話しています。

動的キャストが静的キャストまたは再解釈キャストよりも遅いことは間違いありません。これは、アセンブリレベルでは、後者の2つが割り当てになり(非常に高速で、1クロック目盛りの順序)、動的キャストが必要だからです。オブジェクトを検査して実際のタイプを判別するコード。

実際の速度が遅いとは言えません。おそらくコンパイラによって異なります。そのコード行に対して生成されたアセンブリコードを確認する必要があります。しかし、私が言ったように、呼び出しあたり50ナノ秒は、合理的であると期待されるものの上限です。

6
Eugene Smith

状況を控えめにするために、走行距離は異なる場合があります。

Dynamic_castのパフォーマンスは、何をしているのかに大きく依存し、クラスの名前が何であるかに依存する可能性があります(そして、_reinterpet_cast_に対する時間の比較は、ほとんどの場合、実用的な目的でゼロの命令をとるため、奇妙に見えます、たとえばunsignedからintへのキャストと同様に)。

私はそれがclang/g ++でどのように機能するかを調査してきました。あなたが_dynamic_cast_から_B*_に_D*_ ingしていると仮定します。ここで、BDの(直接または間接的な)ベースであり、複数を無視します-base-classの複雑化、これは次のようなことを行うライブラリー関数を呼び出すことによって機能するようです:

_for dynamic_cast<D*>(  p  )   where p is B*

type_info const * curr_typ = &typeid( *p );
while(1) {
     if( *curr_typ == typeid(D)) { return static_cast<D*>(p); } // success;
     if( *curr_typ == typeid(B)) return nullptr;   //failed
     curr_typ = get_direct_base_type_of(*curr_typ); // magic internal operation
}
_

つまり、はい、_*p_が実際にDである場合は、かなり高速です。成功した_type_info_比較は1つだけです。最悪のケースは、キャストが失敗し、DからBまでの多くのステップがある場合です。この場合、多くの失敗したタイプ比較があります。

型の比較にはどのくらい時間がかかりますか?これは、clang/g ++上で行われます。

_compare_eq( type_info const &a, type_info const & b ){
   if( &a == &b) return true;   // same object
   return strcmp( a.name(), b.name())==0;
}
_

同じタイプを表す2つの異なる_type_info_オブジェクトが存在する可能性があるため、strcmpが必要です(これは、共有ライブラリにあり、他のライブラリにない場合にのみ発生することは確かです)。ただし、ほとんどの場合、タイプが実際に等しい場合、それらは同じtype_infoを参照します。したがって、ほとんどの成功タイプ比較は非常に高速です。

name()メソッドは、クラスのマングル名を含む固定文字列へのポインタを返すだけです。したがって、もう1つの要因があります。DからBに向かう途中のクラスの多くが_MyAppNameSpace::AbstractSyntaxNode<_で始まる名前を持っている場合、失敗した比較は通常よりも時間がかかります。 strcmpは、マングルされた型名の違いに達するまで失敗しません。

そしてもちろん、操作は全体として型階層を表すリンクされたデータ構造の束をトラバースするので、時間はそれらがキャッシュ内で新鮮かどうかによって異なります。したがって、同じキャストを繰り返し行っても、平均時間が表示される可能性が高く、そのキャストの典型的なパフォーマンスを必ずしも表すとは限りません。

3
greggo