私はこの記事を読んでいましたが、この男は、データ指向設計とOOPを組み合わせると誰もがどのように大きな利益を得ることができるかについて話し続けています。ただし、彼はコードサンプルを表示していません。
私はこれをグーグルで検索しましたが、コードサンプルは言うまでもなく、これが何であるかについての実際の情報を見つけることができませんでした。この用語に精通していて、例を提供できる人はいますか?これはおそらく他の何かの別の言葉ですか?
私はこの記事を読んでいましたが、この男は、データ指向設計とOOPを組み合わせると誰もがどのように大きな利益を得ることができるかについて話し続けています。ただし、彼はコードサンプルを表示していません。
私はこれをグーグルで検索しましたが、コードサンプルは言うまでもなく、これが何であるかについての実際の情報を見つけることができませんでした。この用語に精通していて、例を提供できる人はいますか?これはおそらく他の何かの別の言葉ですか?
まず第一に、これをデータ駆動型設計と混同しないでください。
データ指向設計についての私の理解は、効率的な処理のためにデータを整理することです。特にキャッシュミスなどに関しては、データ駆動型設計は、データにプログラムの多くの動作を制御させることです(Andrew Keithの回答で非常によく説明されています)。
アプリケーションに、色、半径、弾力性、位置などのプロパティを持つボールオブジェクトがあるとします。
OOPでは、次のようなボールを記述します。
class Ball {
Point position;
Color color;
double radius;
void draw();
};
次に、次のようなボールのコレクションを作成します。
vector<Ball> balls;
ただし、データ指向の設計では、次のようなコードを作成する可能性が高くなります。
class Balls {
vector<Point> position;
vector<Color> color;
vector<double> radius;
void draw();
};
ご覧のとおり、1つのボールを表す単一のユニットはもうありません。ボールオブジェクトは暗黙的にのみ存在します。
これには、パフォーマンスの面で多くの利点があります。通常、私たちは同時に多くのボールを操作したいと思っています。ハードウェアは通常、メモリの大きな連続したチャンクが効率的に動作することを望んでいます。
次に、ボールのプロパティの一部にのみ影響する操作を行う場合があります。たとえば、さまざまな方法ですべてのボールの色を組み合わせる場合は、キャッシュに色情報のみを含める必要があります。ただし、すべてのボールプロパティが1つのユニットに格納されている場合は、ボールの他のすべてのプロパティもプルします。あなたはそれらを必要としないのに。
各ボールが64バイトを占め、ポイントが4バイトを占めるとします。キャッシュスロットには、たとえば64バイトも必要です。10個のボールの位置を更新する場合は、10 x 64 = 640バイトのメモリをキャッシュに取り込み、10回のキャッシュミスを取得する必要があります。ただし、ボールの位置を個別のユニットとして処理できる場合は、4 x 10=40バイトしかかかりません。これは1つのキャッシュフェッチに収まります。したがって、10個のボールすべてを更新するためのキャッシュミスは1回だけです。これらの数値は任意です。キャッシュブロックの方が大きいと思います。
ただし、メモリレイアウトがキャッシュヒット、ひいてはパフォーマンスに深刻な影響を与える可能性があることを示しています。これは、CPUとRAMの速度の差が広がるにつれて重要性が増すだけです。
私のボールの例では、問題を大幅に簡略化しました。通常、通常のアプリでは、複数の変数に同時にアクセスする可能性が高いためです。たとえば、位置と半径はおそらく一緒に頻繁に使用されます。次に、構造は次のようになります。
class Body {
Point position;
double radius;
};
class Balls {
vector<Body> bodies;
vector<Color> color;
void draw();
};
これを行う必要がある理由は、一緒に使用されるデータが別々の配列に配置されている場合、それらがキャッシュ内の同じスロットをめぐって競合するリスクがあるためです。したがって、一方をロードすると、もう一方が破棄されます。
したがって、オブジェクト指向プログラミングと比較すると、最終的に作成するクラスは、問題のメンタルモデルのエンティティに関連していません。データはデータ使用量に基づいてまとめられるため、データ指向設計でクラスに付けるための適切な名前が常にあるとは限りません。
データ指向設計の背後にある考え方は、リレーショナルデータベースについての考え方と非常によく似ています。リレーショナルデータベースの最適化には、キャッシュをより効率的に使用することも含まれますが、この場合、キャッシュはCPUキャッシュではなく、メモリ内のページです。優れたデータベース設計者は、使用頻度の低いデータを別のテーブルに分割する可能性があります。これまでに使用された列の数が非常に多いテーブルを作成するのではありません。また、ディスク上の複数の場所からデータにアクセスする必要がないように、一部のテーブルを非正規化することもできます。データ指向設計の場合と同様に、これらの選択は、データアクセスパターンとパフォーマンスのボトルネックがどこにあるかを調べることによって行われます。
Mike Actonは最近、データ指向の設計について公開講演を行いました。
私の基本的な要約は次のとおりです。パフォーマンスが必要な場合は、データフローについて考え、問題が発生する可能性が最も高いストレージレイヤーを見つけて、最適化するのが難しいでしょう。マイクはリアルタイムで実行しているため、L2キャッシュミスに焦点を当てていますが、データベース(ディスク読み取り)やWeb(HTTP要求)にも同じことが当てはまると思います。これはシステムプログラミングを行うのに便利な方法だと思います。
アルゴリズムや時間の複雑さについて考えることを免れるわけではなく、狂ったCSスキルでターゲットにしなければならない最も高価な操作タイプを見つけることに注意を向けるだけであることに注意してください。
ノエルがゲーム開発で直面する特定のニーズのいくつかについて具体的に話していることを指摘したいと思います。リアルタイムのソフトシミュレーションを行っている他のセクターもこれから恩恵を受けると思いますが、一般的なビジネスアプリケーションに顕著な改善を示す手法になる可能性は低いです。この設定は、パフォーマンスの最後のすべてのビットが基盤となるハードウェアから確実に絞り出されるようにするためのものです。
データ指向の設計とは、アプリケーションのロジックが手続き型アルゴリズムではなく、データセットで構成されている設計です。例えば
手続き型アプローチ。
int animation; // this value is the animation index
if(animation == 0)
PerformMoveForward();
else if(animation == 1)
PerformMoveBack();
.... // etc
データ設計アプローチ
typedef struct
{
int Index;
void (*Perform)();
}AnimationIndice;
// build my animation dictionary
AnimationIndice AnimationIndices[] =
{
{ 0,PerformMoveForward }
{ 1,PerformMoveBack }
}
// when its time to run, i use my dictionary to find my logic
int animation; // this value is the animation index
AnimationIndices[animation].Perform();
このようなデータ設計は、アプリケーションのロジックを構築するためのデータの使用を促進します。特に、アニメーションやその他の要素に基づいて何千ものロジックパスがある可能性のあるビデオゲームでは、管理が簡単です。
最新のプロセッサアーキテクチャを利用したい場合は、特定の方法でデータをメモリに配置する必要があります。CPUは、メモリ内に順番に配置される単純な型の処理に非常に優れています。他のレイアウトは、処理コストがはるかに高くなります。
オブジェクト指向のアプローチでは、常に1つのインスタンスについて考え、次にオブジェクトをコレクションにグループ化することによって、それを複数のインスタンスに拡張します。しかし、ハードウェアの観点からは、これには追加のコストが伴います。
データ指向アプローチでは、オブジェクト指向プログラミングの場合と同じように「インスタンス」はありません。インスタンスは、リレーショナルデータベースのデータと同様に識別子を持つことができますが、それとは別に、インスタンスに関連するデータを複数のテーブルに分割して(テーブルはベクターとして実装されます)、効率的な処理を可能にします。
例:クラスStudent{intid;があると想像してください。std :: string name; フロート平均; bool卒業; }。OOPの場合、すべての生徒を1つのベクトルに配置します。
データ指向の設計では、最初に、このデータに対してどのような処理を実行するかを自問します。まだ卒業していないすべての学生の平均点を計算するとします。したがって、卒業した学生と卒業していない学生のみを含むテーブルを作成します。学生名は処理に使用されないため、そのテーブルに保持することはできません。ただし、学生証と平均点を表に残しておきます。
ここで、卒業していない学生の平均点を計算することは、卒業していないテーブルを繰り返して計算を実行することを意味します。平均マークはメモリ内で隣接しているため、CPUはSIMDを使用し、可能な限り最も効率的な方法でデータを処理します。学生が卒業したかどうかをテストするために卒業したboolにクエリを実行していないため、データキャッシュのミスはありません。
これは理論的にはいいように聞こえますが、私は実際のプロジェクトでこの種の開発を行ったことがありません。誰か経験があれば、私に連絡してください、私は多くの質問があります。