48

質問を読む前に:
この質問は、を使用することがどれほど役立つかについてではありません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;

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

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

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

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

4

6 に答える 6

59

まず、結果はタイマーの解像度によって左右されるため、数回の反復だけでなく、はるかに多くのパフォーマンスを測定する必要があります。代表的な写真を作成するために、たとえば100万以上を試してください。また、この結果は、何かと比較しない限り意味がありません。つまり、同等のことを実行しますが、動的キャストは行いません。

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

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

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

于 2010-10-29T10:20:46.077 に答える
32

同等の機能を比較しなければ、パフォーマンスは無意味です。 ほとんどの人は、dynamic_castは同等の動作と比較せずに遅いと言います。これで彼らを呼びなさい。別の言い方をすれば:

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

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

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

于 2010-10-29T10:22:37.570 に答える
26

ここにいくつかのベンチマークがあります:
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回のキャストで100nsのオーダーです。74ミリ秒のような値は現実に近いようには見えません。

于 2011-09-28T06:40:51.533 に答える
7

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

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

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

于 2010-10-29T10:23:30.303 に答える
7

状況を過小評価するために、マイレージは異なる場合があります。

dynamic_castのパフォーマンスは、実行している内容に大きく依存し、クラスの名前に依存する可能性があります(また、reinterpet_castほとんどの場合、実際の目的で命令がゼロになるため、時間の比較は奇妙に思えます。たとえば、unsignedからキャストint)。

私はそれがclang/g++でどのように機能するかを調べてきました。の(直接または間接)ベースであるdynamic_castaからaB*D*移動し、複数のベースクラスの複雑さを無視すると仮定すると、次のようなことを行うライブラリ関数を呼び出すことで機能するようです。BD

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です; たった1つの成功したtype_info比較。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;
}

strcmpが必要なのtype_info.name()は、同じタイプを提供する2つの異なる文字列オブジェクトを持つことができるためです(ただし、これは一方が共有ライブラリにあり、もう一方がそのライブラリにない場合にのみ発生することは間違いありません)。ただし、ほとんどの場合、型が実際に等しい場合、それらは同じ型名文字列を参照します。したがって、最も成功する型比較は非常に高速です。

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

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

于 2019-11-01T02:53:54.447 に答える
0

質問は代替案について言及していません。RTTIが広く利用可能になる前、または単にRTTIの使用を回避するために、従来の方法では、仮想メソッドを使用してクラスの型をチェックし、必要に応じてチェックstatic_castします。これには、多重継承では機能しないという欠点がありますが、多重継承階層のチェックに時間を費やす必要がないという利点もあります。

私のテストでは:

  • dynamic_cast14.4953ナノ秒で実行されます。
  • 仮想メソッドのチェックとstatic_castingは、約2倍の速度6.55936ナノ秒で実行されます。

これは、最適化を無効にして次のコードを使用して、有効:無効のキャストの比率を1:1でテストするためのものです。パフォーマンスチェックにはWindowsを使用しました。

#include <iostream>
#include <windows.h>


struct BaseClass
{
    virtual int GetClass() volatile
    { return 0; }
};

struct DerivedClass final : public BaseClass
{
    virtual int GetClass() volatile final override
    { return 1; }
};


volatile DerivedClass *ManualCast(volatile BaseClass *lp)
{
    if (lp->GetClass() == 1)
    {
        return static_cast<volatile DerivedClass *>(lp);
    }

    return nullptr;
}

LARGE_INTEGER perfFreq;
LARGE_INTEGER startTime;
LARGE_INTEGER endTime;

void PrintTime()
{
    float seconds = static_cast<float>(endTime.LowPart - startTime.LowPart) / static_cast<float>(perfFreq.LowPart);
    std::cout << "T=" << seconds << std::endl;
}

BaseClass *Make()
{
    return new BaseClass();
}

BaseClass *Make2()
{
    return new DerivedClass();
}


int main()
{
    volatile BaseClass *base = Make();
    volatile BaseClass *derived = Make2();
    int unused = 0;
    const int t = 1000000000;

    QueryPerformanceFrequency(&perfFreq);
    QueryPerformanceCounter(&startTime);

    for (int n = 0; n < t; ++n)
    {
        volatile DerivedClass *alpha = dynamic_cast<volatile DerivedClass *>(base);
        volatile DerivedClass *beta = dynamic_cast<volatile DerivedClass *>(derived);
        unused += alpha ? 1 : 0;
        unused += beta ? 1 : 0;
    }


    QueryPerformanceCounter(&endTime);
    PrintTime();
    QueryPerformanceCounter(&startTime);

    for (int n = 0; n < t; ++n)
    {
        volatile DerivedClass *alpha = ManualCast(base);
        volatile DerivedClass *beta = ManualCast(derived);
        unused += alpha ? 1 : 0;
        unused += beta ? 1 : 0;
    }

    QueryPerformanceCounter(&endTime);
    PrintTime();

    std::cout << unused;

    delete base;
    delete derived;
}

于 2021-06-02T09:24:43.033 に答える