1

邪魔にならないように...

Premature optimization is the root of all evil

Make use of OOP

etc.

理解します。今後の参考のために、灰白質に保存できる特定の操作の速度に関するアドバイスを探しています。

Animation クラスがあるとします。アニメーションは、ループする (何度も再生する) か、ループしない (1 回再生する) か、一意のフレーム時間を持つかどうかなどを指定できます。これらの「どちらかまたは」属性が 3 つあるとします。Animation クラスのどのメソッドも、せいぜいこれらのいずれかをチェックすることに注意してください (つまり、これは if-elseif の巨大なブランチの場合ではありません)。

ここにいくつかのオプションがあります。

1) 上記の属性のブール値メンバーを与え、適切なアクションを実行するためにアニメーションを再生するときに if ステートメントを使用してそれらをチェックします。

  • 問題: アニメーションが再生されるたびに条件がチェックされます。

2) 基本アニメーション クラスを作成し、LoopedAnimation や AnimationUniqueFrames などの他のアニメーション クラスを派生させます。

  • 問題: vector<Animation>. また、考えられるすべての組み合わせに対して個別のクラスを作成すると、コードが肥大化するように見えます。

3) テンプレートの特殊化を使用し、それらの属性に依存する関数を特殊化します。のようにtemplate<bool looped, bool uniqueFrameTimes> class Animation

  • 問題: これに関する問題は、vector<Animation>何かのアニメーションに対して を使用できないことです。むくむこともあります。

これらのオプションのそれぞれがどのような速度を提供するのだろうか? 3 番目のオプションでは s の一般的なコンテナーを反復処理できないため、1 番目と 2 番目のオプションに特に興味がありますAnimation

要するに、vtable fetch と条件付きのどちらが高速ですか?

4

4 に答える 4

4

(1) 最近では、生成されたアセンブリのサイズはもはや重要ではありませんが、これが生成するものです (おおよそ、x86 上の MSVC を想定):

mov eax, [ecx+12]   ; 'this' pointer stored in ecx, eax is scratch
cmp eax, 0          ; test for 0 
jz  .somewhereElse  ; jump if the bool isn't set

最適化コンパイラはそこに他の命令を散在させ、パイプラインをより使いやすくします。いずれにせよ、クラスのコンテンツはキャッシュにある可能性が高く、そうでない場合でも、とにかく数サイクル後に必要になります。したがって、振り返ってみると、それはおそらく数サイクルであり、フレームごとに最大で数回呼び出されるものについては、それは何もありません.

(2) これは、play() メソッドが呼び出されるたびに生成されるおおよそのアセンブリです。

mov  eax, [ebp+4]    ; pointer to your Animation* somewhere on the stack, eax is scratch
mov  eax, [eax+12]   ; dereference the vtable
call eax             ; call it

次に、重複したコードまたは特殊な play() 関数内に別の関数呼び出しが含まれます。これは、いくつかの一般的なものが明確に存在するため、(コードサイズおよび/または実行速度で) オーバーヘッドが発生するためです。したがって、これは明らかに遅くなります。

また、これにより、一般的なアニメーションの読み込みが非常に難しくなります。グラフィック部門は満足しません。

(3) これを効果的に使用するには、仮想関数 (その場合は (2) を参照) を使用して、テンプレート化されたバージョンの基本クラスを作成するか、場所の型をチェックして手動で行います。アニメーションのことを呼び出します。その場合は、(2) も参照してください。

これにより、一般的なアニメーションの読み込みが非常に難しくなります。グラフィック部門はさらに満足できなくなります。

(4) 心配する必要があるのは、フレームにせいぜい数回しか行われない小さなことのマイクロ最適化ではありません。あなたの投稿を読んで、見過ごされがちな別の問題を実際に特定しました。std::vector<Animation> について言及しています。STLに反対するものは何もありませんが、それは悪いブードゥーです. 単一のメモリ割り当ては、おそらくアプリケーションの実行中ずっと、 play() または update() メソッドのすべてのブール値チェックよりも多くのサイクルを消費します。アニメーションを std::vectors の内外に配置すると (特に、インスタンスへのポインター (スマートまたはダム) ではなくインスタンスを配置する場合) は、さらにコストがかかります。

最適化するには、さまざまな場所を調べる必要があります。これは非常にばかげたマイクロ最適化であり、一般化が難しくなり、グラフィック部門を満足させる以外に何のメリットもありません。ただし、問題になるのはメモリの割り当てについて心配することです。その部分のプログラミングが完了したら、プロファイラーを起動して、ホット スポットがどこにあるかを調べます。

アニメーションを保持することが実際にボトルネックになっている場合は、std::vector (そのままでよい) を参照することをお勧めします。たとえば、侵入型のリンク リストを見たことがありますか? それは実際にこれについて心配するよりも多くの利点があります.

于 2010-05-13T23:43:43.037 に答える
3

(簡潔にするために編集されています。)

コンパイラ、CPU、および OS はすべて、ここで答えを変更できます。

  • CPU: 命令/データ キャッシュのサイズ、アーキテクチャ、および動作、特にインテリジェントなプリフェッチ
  • CPU: 分岐予測と投機的実行動作
  • CPU: 分岐の予測を誤った場合のペナルティ
  • コンパイラと CPU: 条件付きで実行される命令の可用性と相対的なコスト (少数の命令のみをカバーする分岐ケースに役立ちます)
  • コンパイラまたはリンカー: コードを変換して分岐を削除する可能性がある最適化

要するに、Blindy がコメントで言ったように、テストしてください。=)

最新のデスクトップ OS や複数の OS 向けに書いている場合は、プロファイリング ツール (valgrind、shark、codeanalyst、vtune など) の助けを借りてください。ミス、分岐の予測ミスなど。

優れた答えが見つからなくても、ツールを適用することで何かを学ぶことができます。逆アセンブルを見ることも非常に有益であることがよくあります(このスレッドの他の回答のいくつかを参照してください)。

もう少し投機的なメモ:

  • vtable は、ロード (this+0)、オフセット、2 番目のロード、およびレジスタの内容での分岐という結果になる傾向があります。これは、他のいくつかの回答で確認できます。私がよく知っているほとんどの CPU は、レジスタからの分岐の予測が苦手です。
  • bool は、使用している他のデータの近くにある可能性があり、そのため既にキャッシュされている可能性があります。分岐ターゲットも固定される可能性が高いため、予測や投機的実行にはるかに適しています。
  • 一部のプロセッサでは (最近ではまれです)、int よりも bool をロードする方がコストがかかります。
  • 私が使用しているARMプロセッサでは、vtablesをプロセッサコアの「密結合メモリ」に入れることがあります。間接的な読み込み時間を大幅に短縮します。まるで vtable が常にキャッシュ内かそれ以上であるかのようです。

あなたが述べたように、通常のルールが適用されます: 要件に適合し、柔軟/保守可能/読み取り可能であることを最初に行い、次に最適化します。

さらに読む / 追求するその他のパタ​​ーン:

「データ指向設計」と「コンポーネントベースのエンティティ」の両方のパラダイムは、ゲーム、マルチメディア エンジン、およびパフォーマンスに対する平均以上の要求があり、それでもパフォーマンスを維持したいその他のものについて頭に留めておくのに役立ちます。コードはやや整理されています。もちろんYMMVです。=)

于 2010-05-13T23:58:09.537 に答える
2

Vtable は非常に高速です。単純な条件も同様です。これらは、1 桁の CPU 命令に変換されます。この種のパフォーマンスを心配すると、コンパイラーの最適化の濁った水域に陥り、コンパイラーが何をしているかをまったく理解できなくなります。おそらく、プログラムの非常に微妙な変更が、if ステートメントと vtable の間のわずかな違いに勝る可能性があります。

少しに、RTTI 複数ディスパッチと vtable の違いをテストするためのテストを行いました。リリース モードでは、200 万回以上の反復で行われる 3 つのオブジェクト間のディスパッチ (2 つの vtable 呼び出し) に 62 ミリ秒かかります。それは心配する価値さえありません。

于 2010-05-13T23:44:28.807 に答える
0

3 番目の理由で、アニメーションの一般的なコンテナーを持つことが不可能になると誰が言いますか? 使用できるアプローチはいくつかあります。それらはすべて、最終的にポリモーフィック呼び出しを行うことに要約されますが、オプションはそこにあります。このことを考慮:

std::vector<boost::any> generic_container;
function(generic_container[0]);

void function(boost::any & a)
{
  my_operation::execute(a.type().name(), a);
}

my_operation には、操作をタイプ名で登録およびフィルタリングする方法が必要です。a が表すものを操作するファンクタを検索し、それを使用します。次に、ファンクターは適切な時間に any_casts を実行し、型固有の操作を実行します。

または、ビジター フレームワークを使用します。上記は一種のバリエーションですが、あまりにも一般的すぎて実際に資格を得ることができません.

そして、さらに可能な方法があります。アニメーションを保存する代わりに、詳細を非表示にし、アクティブ化されたときに正しい表示オプションを実行するタイプを保存できます。1 つの仮想が呼び出されますが、より複雑な操作を相互に実行する具体的な型を切り替えることに固有です。

言い換えれば、あなたの質問に対する一般的な答えはありません。必要なものに応じて、あらゆる種類の複雑さのレベルに達することができ、実行時とは対照的に、ほぼすべてのプログラムをコンパイル時にポリモーフィックにすることができます。

于 2010-05-13T23:52:18.553 に答える