セットアップ
エンティティコンポーネントアーキテクチャがあり、エンティティは一連の属性(動作のない純粋なデータ)を持つことができ、そのデータに作用するエンティティロジックを実行するシステムが存在します。基本的に、やや疑似コードで:
Entity
{
id;
map<id_type, Attribute> attributes;
}
System
{
update();
vector<Entity> entities;
}
すべてのエンティティに沿って一定の速度で移動するシステムは、
MovementSystem extends System
{
update()
{
for each entity in entities
position = entity.attributes["position"];
position += vec3(1,1,1);
}
}
基本的に、私はupdate()をできるだけ効率的に並列化しようとしています。これは、システム全体を並行して実行するか、1つのシステムの各update()にいくつかのコンポーネントを与えることにより、異なるシステムが同じシステムの更新を実行できるようにして、そのシステムに登録されたエンティティの異なるサブセットに対して実行できます。
問題
示されているMovementSystemの場合、並列化は簡単です。エンティティは相互に依存せず、共有データを変更しないため、すべてのエンティティを並行して移動できます。
ただし、これらのシステムでは、エンティティが相互にやり取りする(データを読み書きする)ことが必要な場合があります。同じシステム内でも、相互に依存する異なるシステム間である場合がよくあります。
たとえば、物理システムでは、エンティティが互いに相互作用する場合があります。2つのオブジェクトが衝突し、それらの位置、速度、およびその他の属性がオブジェクトから読み取られて更新され、更新された属性が両方のエンティティに書き戻されます。
エンジンのレンダリングシステムがエンティティのレンダリングを開始する前に、他のシステムが実行を完了するのを待って、関連するすべての属性が必要な属性であることを確認する必要があります。
これを盲目的に並列化しようとすると、異なるシステムが同時にデータを読み取り、変更する可能性がある従来の競合状態が発生します。
理想的には、他のシステムが同じデータを同時に変更することを心配することなく、またプログラマが実行と並列化を適切に順序付けすることを気にすることなく、すべてのシステムが必要なエンティティからデータを読み取ることができるソリューションが存在しますこれらのシステムを手動で(場合によっては不可能になることもあります)。
基本的な実装では、これはすべてのデータの読み取りと書き込みをクリティカルセクションに配置するだけで実現できます(ミューテックスで保護します)。しかし、これは大量のランタイムオーバーヘッドを引き起こし、パフォーマンスに敏感なアプリケーションにはおそらく適していません。
解決?
私の考えでは、考えられる解決策は、データの読み取り/更新と書き込みが分離されているシステムであり、1つの高価なフェーズでは、システムはデータの読み取りと計算に必要なものだけを計算し、何らかの方法で結果をキャッシュしてからすべて書き込みます。別の書き込みパスで変更されたデータをターゲットエンティティに戻します。すべてのシステムは、フレームの先頭にある状態でデータに作用し、その後、フレームの終わりの前に、すべてのシステムが更新を完了すると、シリアル化された書き込みパスが発生し、すべての異なるキャッシュ結果がキャッシュされますシステムは反復され、ターゲットエンティティに書き戻されます。
これは、簡単な並列化の勝利が結果のキャッシュと書き込みパスのコスト(実行時のパフォーマンスとコードのオーバーヘッドの両方の点で)を上回るほど大きくなる可能性がある(多分間違っているか?)という考えに基づいています。
質問
最適なパフォーマンスを実現するために、このようなシステムをどのように実装するのでしょうか?そのようなシステムの実装の詳細と、このソリューションを使用するエンティティコンポーネントシステムの前提条件は何ですか?