では、DOD とは何でしょうか。明らかに、それはパフォーマンスに関するものですが、それだけではありません。それはまた、読みやすく、理解しやすく、再利用さえ可能な、適切に設計されたコードに関するものでもあります。オブジェクト指向設計とは、カプセル化された仮想「オブジェクト」に収まるようにコードとデータを設計することです。各オブジェクトは、オブジェクトが持つ可能性のあるプロパティの変数と、それ自体または世界の他のオブジェクトに対してアクションを実行するメソッドを持つ個別のエンティティです。OO 設計の利点は、私たちの周りの (現実の) 世界全体が同じように機能しているように見えるため、コードをオブジェクトにモデル化するのが簡単であることです。互いに相互作用できるプロパティを持つオブジェクト。
問題は、コンピュータの CPU がまったく異なる方法で動作することです。同じことを何度も繰り返させると、最も効果的です。何故ですか?キャッシュと呼ばれる小さなもののため。最新のコンピューターで RAM にアクセスするには、100 または 200 の CPU サイクルが必要になる場合があり (CPU はその間ずっと待機する必要があります!)、これは長すぎます。そのため、CPU には非常に高速にアクセスできるメモリのごく一部、つまりキャッシュ メモリがあります。問題は、それがほんの数MBのトップであることです。そのため、キャッシュにないデータが必要になるたびに、RAM にアクセスする必要があります。これはデータだけでなく、コードにも当てはまります。命令キャッシュにない関数を実行しようとすると、コードが RAM からロードされている間にストールが発生します。
オブジェクト指向プログラミングに戻ります。オブジェクトは大きいですが、ほとんどの関数はそのデータのごく一部しか必要としないため、不要なデータをロードしてキャッシュを浪費しています。メソッドは、他のメソッドを呼び出す他のメソッドを呼び出し、命令キャッシュをスラッシングします。それでも、私たちは同じことを何度も何度も繰り返します。たとえば、ゲームの弾丸を見てみましょう。単純な実装では、各弾丸が個別のオブジェクトになる可能性があります。バレットマネージャークラスがあるかもしれません。最初の箇条書きの update 関数を呼び出します。方向/速度を使用して 3D 位置を更新します。これにより、オブジェクトからの他の多くのデータがキャッシュにロードされます。次に、World Manager クラスを呼び出して、他のオブジェクトとの衝突をチェックします。これにより、他の多くのものがキャッシュにロードされ、おそらく、元の Bullet Manager クラスのコードが命令キャッシュから削除されることさえあります。Bullet の更新に戻ります。衝突はなかったので、Bullet Manager に戻ります。一部のコードを再度ロードする必要がある場合があります。次に、箇条書き 2 の更新です。これにより、大量のデータがキャッシュに読み込まれ、世界が呼び出されます... などです。したがって、この仮想的な状況では、コードの読み込みに 2 つのストールが発生し、データの読み込みに 2 つのストールが発生したとします。これは、1 発の弾丸に対して少なくとも 400 サイクルが無駄になっており、他の何かに命中した弾丸は考慮していません。現在、CPU は 3 GHz 以上で動作するため、弾丸は 1 つもありませんが、弾丸が 100 個ある場合はどうでしょうか。それともそれ以上?箇条書き 2 の更新。これにより、大量のデータがキャッシュに読み込まれ、世界が呼び出されます... などです。したがって、この仮想的な状況では、コードの読み込みに 2 つのストールが発生し、データの読み込みに 2 つのストールが発生したとします。これは、1 発の弾丸に対して少なくとも 400 サイクルが無駄になっており、他の何かに命中した弾丸は考慮していません。現在、CPU は 3 GHz 以上で動作するため、弾丸は 1 つもありませんが、弾丸が 100 個ある場合はどうでしょうか。それともそれ以上?箇条書き 2 の更新。これにより、大量のデータがキャッシュに読み込まれ、世界が呼び出されます... などです。したがって、この仮想的な状況では、コードの読み込みに 2 つのストールが発生し、データの読み込みに 2 つのストールが発生したとします。これは、1 発の弾丸に対して少なくとも 400 サイクルが無駄になっており、他の何かに命中した弾丸は考慮していません。現在、CPU は 3 GHz 以上で動作するため、弾丸は 1 つもありませんが、弾丸が 100 個ある場合はどうでしょうか。それともそれ以上?弾丸は100発?それともそれ以上?弾丸は100発?それともそれ以上?
ですから、これは多くの物語がある場所です。はい、オブジェクト、マネージャ クラス、ファイル アクセスなどしか取得していない場合もあります。しかし、多くの場合、同様のケースが多数あります。ナイーブな、またはナイーブではないオブジェクト指向設計でさえ、多くの問題を引き起こします。そこで、データ指向設計に入ります。DOD の鍵は、OO 設計のように逆ではなく、データに基づいてコードをモデル化することです。これは設計の最初の段階から始まります。最初に OO コードを設計してから最適化するわけではありません。まず、データをリストして調べ、どのように変更したいかを考えることから始めます (すぐに実用的な例を取り上げます)。コードがデータをどのように変更するかがわかったら、可能な限り効率的に処理できるようにコードをレイアウトできます。今では、これはコードとデータのひどいスープにつながるだけだと思うかもしれませんが、それは設計が悪い場合に限られます (OO プログラミングでは、悪い設計は同じように簡単です)。適切に設計すれば、コードとデータを特定の機能に合わせて適切に設計できるため、非常に読みやすく、再利用しやすいコードになります。
弾丸に戻りましょう。弾丸ごとにクラスを作成する代わりに、弾丸マネージャーのみを保持します。各弾丸には位置と速度があります。各弾丸の位置を更新する必要があります。各弾丸には衝突チェックが必要であり、何かに当たったすべての弾丸はそれに応じて何らかのアクションを実行する必要があります。したがって、この説明を見るだけで、このシステム全体をより良い方法で設計できます。すべての弾丸の位置を配列/ベクトルに入れましょう。すべての弾丸の速度を配列/ベクトルに入れましょう。それでは、これら 2 つの配列全体を繰り返し処理し、対応する速度で各位置の値を更新することから始めましょう。これで、データ キャッシュに読み込まれたすべてのデータが、これから使用するデータになります。事前にいくつかの配列データを事前にロードするためのスマートな事前ロード コマンドを配置することもできます。私たちがそれに到達すると、キャッシュに入れられます。次に衝突チェック。ここでは詳しく説明しませんが、すべての箇条書きを次々に更新するとどのように役立つか想像できます。また、衝突が発生した場合、新しい関数を呼び出したり、何もしないことに注意してください。衝突したすべての弾丸のベクトルを保持するだけで、衝突チェックが完了すると、それらすべてを次々に更新できます。データを別の方法でレイアウトすることで、大量のメモリ アクセスがほとんどない状態になったことがわかりますか? オブジェクト指向の方法で設計されていないにもかかわらず、コードとデータが理解しやすく、再利用しやすいことにも気づきましたか? 新しい関数を呼び出したり、何かをしたりするつもりはありません。衝突したすべての弾丸のベクトルを保持するだけで、衝突チェックが完了すると、それらすべてを次々に更新できます。データを別の方法でレイアウトすることで、大量のメモリ アクセスがほとんどない状態になったことがわかりますか? オブジェクト指向の方法で設計されていないにもかかわらず、コードとデータが理解しやすく、再利用しやすいことにも気づきましたか? 新しい関数を呼び出したり、何かをしたりするつもりはありません。衝突したすべての弾丸のベクトルを保持するだけで、衝突チェックが完了すると、それらすべてを次々に更新できます。データを別の方法でレイアウトすることで、大量のメモリ アクセスがほとんどない状態になったことがわかりますか? オブジェクト指向の方法で設計されていないにもかかわらず、コードとデータが理解しやすく、再利用しやすいことにも気づきましたか?
では、「1 つあるところに複数ある」という話に戻ります。OO コードを設計するときは、プロトタイプ/クラスという 1 つのオブジェクトについて考えます。弾丸には速度があり、弾丸には位置があり、弾丸はその速度によって各フレームを移動し、弾丸は何かに当たる可能性があります。弾丸を移動し、衝突をチェックする update 関数。ただし、複数のオブジェクトがある場合は、それらすべてについて考える必要があります。弾丸には位置、速度があります。一部の弾丸は衝突する場合があります。個々のオブジェクトについて考えなくなったことがわかりますか? 私たちはそれらすべてについて考えており、現在はコードを大幅に異なる方法で設計しています。
これが質問の2番目の部分に答えるのに役立つことを願っています. 各敵を更新する必要があるかどうかではなく、それらを更新する最も効率的な方法についてです。そして、DOD を使用して敵だけを設計してもパフォーマンスはあまり向上しないかもしれませんが、これらの原則 (該当する場合のみ!) に基づいてゲーム全体を設計すると、パフォーマンスが大幅に向上する可能性があります!
質問の最初の部分は、DOD の他の例です。申し訳ありませんが、そこまで多くはありません。Bjoern Knafla によるビヘイビア ツリーのデータ指向設計に関するシリーズ: http://bjoernknafla.com/data-directional-behavior-tree-overview 4 つのシリーズの最初のものから始めるには、記事自体にリンクがあります。古い質問にもかかわらず、これがまだ役立つことを願っています。または、他の SO ユーザーがこの質問に出くわし、この回答から何らかの用途があるかもしれません。