Unity ECS

著者:Pang Wei Wei
リンク:https://www.zhihu.com/question/286963885/answer/452979420
出典:
著者がほぼ著作権を所有していることを知っています。商用の再版の場合は、著者に連絡して許可を求めてください。非商用の再版の場合は、出典を示してください。
 

ECSは2つの問題を解決します。

1)パフォーマンス;

2)不要なメモリ使用量を減らします。

写真を入れて、私は前にデモテストを書きました。ecsを使用するために、ecsを使用しないでください。インスタンス化の最適化の場合のパフォーマンスの違い3。

レンダリングするオブジェクトが500未満の場合、ecsのパフォーマンスは大幅に向上しないことがわかります。1000を超えると、ecsのパフォーマンスに大きな利点があります。10000obj未満では、パフォーマンスのギャップはほぼ100になります。

したがって、objが200以内のゲームの場合、ecsを使用するかどうかはほとんど違いがありません。

また、ecsは統一によって提案された体系的な計画と基準であり、従来の方法を使用して多かれ少なかれ同様の結果を達成することもでき、ecsは必要ありません。

 

デモは次の図(インスタンス化)です。組み込みの回転デモによると、対応するインスタンス化と従来の方法のバージョンが完了しています。このデモは、球が回転する1000個の立方体です。立方体を叩いた後、立方体は次のように回転します。一定期間、徐々に停止するため、更新を続けるには1001個のオブジェクトが必要です。

 

=========社内で共有されている記事全体を補足します。

 

MonoBehaviour、Component、GameObjectは不要になりました

これまで、MonoBehaviourはゲームロジックとデータの2つの機能を備えていました。GameObjectを作成し、MB(MonoBehaviour、以下と同じ)を追加してから、Updateでゲームロジックを更新しました。多くの場合、Updateで一部のデータを更新し、MBを実装します。非常に複雑です。多くの不要な機能が単一の脳に継承されるため、これらの不要な機能を処理するために多くのメモリを浪費する必要がある非常に単純なタスクにつながります。MBを制御不能に使用する場合、基本的には悪夢です。ゲームの効率。

以前のコンポーネントはMBから継承されます。ほとんどの場合、コンポーネントの役割はデータを提供することですが、コンポーネントによって編成された配列は、再計算および更新が必要なデータを編成しないため、CPUキャッシュに適していません。計算を行うときにCPUでキャッシュミスが発生する可能性があります。ゲームロジックでデータを更新するために多数のオブジェクトが必要な場合、消費のこの部分は非常に大きくなる可能性があります。

同時に、MBは実行順序の問題を解決できません。動的に作成されたGameObjectの場合、MBの更新順序は不確実です。相互依存を確保するために、MBの更新と破棄をシステム化することがよくありますが、UnityのMB設計はこれ以上のものではありません。この問題の解決策。スクリプトの実行順序の調整は面倒で役に立たない(前のテストは動的に作成されたMBには影響せず、シーン内の静的MBにのみ使用できます)。

また、ゲーム内に多数のコンポーネントがバインドされた多数のGameObjectがある場合、多数の更新を実行するのに非常に時間がかかり、これらの更新はメインスレッドでのみ実行でき、実行できません。平行であること。

この目的のために、Unity 2018.2は、上記の問題を解決するためのまったく新しいECSシステムを導入しました。

完全にデータ駆動

ECSの中心的な設計目標は、従来のMB、GameObject、およびComponentオブジェクトの構造を取り除き、完全にデータ駆動型のコード編成に変更することです。このような変更により、2つの問題を解決できます。

1)データをより効果的に整理し、CPUキャッシュの使用率を向上させます。

2)並列化。

例を簡単に見てみましょう。これは、ECSサンプルに付属している例です。

 

この例では、画面に1000を超えるオブジェクトがありますが、対応する1000のゲームオブジェクトがないことがわかります。球がブロックに当たると、回転後の減衰が発生し、フレームレートを維持できます。 300〜600fps。以前は、同様の効果を実現するために1000個のGameObjectを作成する必要があり、その後1000 MBがGameObjectの変換情報の更新を担当しました。このメソッドに従ってこのメソッドを実装したところ、このデモは約3分の1にすぎません。 fps。

 

上の図では、fpsが100〜200 fpsのゲームオブジェクトがさらに作成されることに注意してください。もちろん、この実装は最適ではありません。インスタンス化を使用してドローコールを最適化することもできます。インスタンス化のバージョンを比較するために、次の比較を実装しました。

 

 

fpsは約150〜300 fpsで、インスタンス化はfpsの約1倍であることがわかります。1000objsテストでは、異なる実装方法の違いは約1〜2倍です。違いはそれほど大きくないようです。より高いobjをテストしましたfpsの数、より高いobjsテストでは、次のグラフが得られます。

 

objの数が多いほど、ECSメソッドの利点が反映されていることがわかります.10000obj未満でも、ECSは350の高いfpsを達成でき、インスタンスが最適化されても、残りは4 fpsであり、ほぼ100.回。

ここで最初に戻ります。ECSは次の2つの問題を解決しました。

1)データをより効果的に整理し、CPUキャッシュの使用率を向上させます。

2)並列化。

しかし、それを解決する方法は?

データをより効果的に整理し、CPUキャッシュの使用率を向上させます

従来の方法は、ゲームオブジェクトデータをコンポーネントに配置することです。たとえば、次のようになります(参照1)。

using Unity.Mathematics;
class PlayerData// 48 bytes
{
 public string public int[]
 public quaternion
 public float3
 string name;       // 8 bytes  
 someValues; // 8 bytes
}
PlayerData players[];

彼のメモリレイアウトは次のようになります。

また、この設計はCPUキャッシュに非常に不向きです。float3データをバッチで更新しようとすると、メモリ内の不連続領域に割り当てられるため、CPUキャッシュの予測は常に失敗し、CPUはいくつかのアドレス指定ジャンプを必要とします。対応するデータを操作して、更新が必要なデータをバッチで継続的に保存できれば、CPUキャッシュのヒット率が大幅に向上し、計算速度が向上します。

ECSの設計仕様によれば、更新されたデータのバッチは継続的に抽出されてメモリに配置されるため、図(図)に示すように、キャッシュの先読み中に後続のデータを一度に読み取ることができ、CPU操作の効率が向上します。参考1):

 

実際の計算では、図に示すように、各エンティティのすべてのデータを更新するのではなく、間接インデックスを使用してすべてのエンティティのさまざまなタイプのデータを更新します。

ここでわかるように、最大​​の変更点は次のとおりです。

システムの更新計算(前のコンポーネントと同等)では、すべてのエンティティの位置がまとめられ、バッチで更新されます。これは、これらの位置のfloat3データが内部で連続しており、CPU操作が最速であるためです。

並列化

データを個別に抽出した後、次のタスクは、これらのデータ計算を並列化し、複数のコアを最大限に活用することです。以前は、ほとんどのロジックコードがメインスレッドで実行されていました。もちろん、一部のプロジェクトチームはこの問題を認識し、表示とは関係のないロジックを複数のスレッドに並列に配置しましたが、データを完全に抽象化することはできませんでした。完全なセットを形成します。開発フレームワーク、およびECSはこの問題を解決します。

ECSは、下部にジョブシステムを開き、上部にマルチスレッドスケジューリングシステムのセットであるc#ジョブシステムを提供します。異なるデータが相互に依存していない場合は、次のように、並列コンピューティング用のc#ジョブシステムを介してこれらのデータを複数のスレッドに配置するだけで済みます。

public class RotationSpeedSystem : JobComponentSystem
    {
        struct RotationSpeedRotation : IJobProcessComponentData<Rotation, RotationSpeed>
        {
            public float dt;

            public void Execute(ref Rotation rotation, [ReadOnly]ref RotationSpeed speed)
            {
                rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.axisAngle(math.up(), speed.Value * dt));
            }
        }

        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            var job = new RotationSpeedRotation() { dt = Time.deltaTime };
            return job.Schedule(this, 64, inputDeps);
        } 
    }

異なるデータに依存関係があり、計算を完了するために他のデータを計算する必要がある場合は、タスクの依存関係を設定できます。c#ジョブシステムは、そのようなタスクの呼び出しと依存関係の並べ替えを自動的に完了します。

ミックスモード

従来の方法で開発されたコードはすでに多数あります。ECSの効率を享受したい場合は、既存のコードを大幅に変換する必要があります。既存のコードを変更せずに改善する比較的簡単な方法はありますか。 、答えは、ECSが互換性のあるハイブリッドモードを提供するということです。

たとえば、次のコード(参照2):

using Unity.Entities;using UnityEngine;
class Rotator : MonoBehaviour{
 // The data - editable in the inspector public float Speed;
}
class RotatorSystem : ComponentSystem{
 struct Group
    {
 // Define what components are required for this  // ComponentSystem to handle them. public Transform Transform;
 public Rotator   Rotator;
    }

 override protected void OnUpdate()
 {
 float deltaTime = Time.deltaTime;

 // ComponentSystem.GetEntities<Group>  // lets us efficiently iterate over all GameObjects // that have both a Transform & Rotator component  // (as defined above in Group struct). foreach (var e in GetEntities<Group>())
        {
            e.Transform.rotation *= Quaternion.AngleAxis(e.Rotator.Speed * deltaTime, Vector3.up);
        }
    }
}

主な変更点は、MBのUpdate関数をComponentSystemのOnUpdate関数に移動し、MBとComponentSystemの間でデータを交換するためのグループ構造体を追加し、GameObjectEntityコンポーネントを元のGameObjectに追加することです。このコンポーネントの目的は他のすべてを抽出することです。 GameObjectのコンポーネントを作成し、エンティティを作成して、GetEntities関数を介してComponentSystem内の対応するGameObjectをトラバースできるようにします。

Unityは、起動時にGameObjectEntityコンポーネントがマウントされたすべてのGameObjectに対応するComponentSystemを作成するため、以前のGameObject.Instantiateメソッドを使用してGameObjectを作成できます。元のMBの更新機能がComponentSystemのOnUpdate機能に置き換えられただけです。

この変更により、元のコード結果を維持しながら、ECSの機能の一部を組み合わせることができます。

混合モードでは、データと動作を分離し、オブジェクトのデータをバッチで更新し、各オブジェクトの仮想メソッド呼び出し(関数に抽象化)を回避し、引き続きインスペクターを使用してのプロパティとデータを監視できます。 GameObject。

ただし、この変更によって完全に改善されることはなく、作成時間と読み込み時間も改善されず、データアクセスはキャッシュに対応していませんでした。また、データはメモリ内に継続的に配置されず、並列化もされませんでした。

この変更は、純粋なECSによって実行される段階的なコードリファクタリングに近づくことであるとしか言えません。最終的には、純粋なECSを使用して、最大のパフォーマンス向上を実現する必要があります。

関連資料

1)Unityは、ppt、ECS、およびジョブシステムを公式に共有しています。

2)https://github.com/Unity-Technologies/EntityComponentSystemSamples/

おすすめ

転載: blog.csdn.net/Momo_Da/article/details/112906063