はじめに
こちらの記事は先日行われた"第四十八回Unityもくもく会"にて発表したものの、詳細解説になります。
ECSって何だろう
- Entity Component Systemの略
- Unityに新しく追加される情報処理形態の一つ
- ECS自体は昔からあるゲーム内オブジェクト処理記述方法の一つ[^1]
GameObject+MonoBehaviourの頃と何が違うのか
- 中の人達が解説してくれているので、そちらを当たるといいです[^3]
実際の仕組みを知りたいな
- ソースを読む
- パッケージマネージャからEntitiesを追加
${ProjectFolder}/Library/PackageCache/com.unity.entities@(バージョン)/Unity.Entities
以下にそのままソースが入っています
- 読んでみてだいたい分かったこと
- ECSはJobSystem+PlayerLoopSystemの上に構築されている
- ネイティブプラグインはなし
- Unity本体の隠し機能などは利用されていない
- unsafe祭り
- メモリ確保はNative系
だいたい分かったことの詳細
- JobSystem+PlayerLoopSystemの上に構築されている
- 登録されたEntityやComponentに対する処理
- PlayerLoopSystemでUpdate()の辺りに差し込まれ、毎フレーム自動で実行される
- 複数のEntityに対する処理
- JobSystemでUpdate
- 登録されたEntityやComponentに対する処理
- ネイティプラグインはなし
- パッケージ内は
- 依存パッケージまでは追わず
- Unity本体の隠し機能などは利用されていない
- 見た感じ
- unsafe祭り
- メモリ確保はNative系
- 上記二点のお陰で、非常に読みにくいソースになっている
だいたい分かったことから思いついたこと
- 自分で実装できるんじゃない?
してみました
https://github.com/fum1h1ro/SimpleECS
Unity2018.3.0b5で作業しましたが、特殊なものはNativeCollectionくらいしか使ってないので、2018.1辺りでも動くはずです。
サンプルとして、100x100=10,000個のCubeをサインカーブで動かしています。
また同等のものを、10,000個のGameObject+MonoBehaviourで実装しており、ボタンを押すことで切り替えることが出来るようになってます。
簡単な解説
1 | void Awake() { |
コメントが皆無で申し訳ないのですが、Awake()
にて各種の準備を行って、Update()
でEntityの更新を行うようになっています。
World
クラスは本家と同じで、Entity
を束ねる役目を果たしています。クラス名は本家に近いものになっていますが、自動呼び出しなどがないので、自分で更新を呼んでやる必要があります。
MoveSystem
で位置情報を更新し、MakeMatrixSystem
でMatrix4x4
を作っています。
描画部分が変な感じのループになっているのは、Graphics.DrawMeshInstanced()
が、1023個までしか受け付けてくれないため、10,000個を1023ずつ分割して描画しているためです。[^2]
実装
ソースを見てもらうとわかると思いますが、800行程度の1ファイルに収まっています。
(詳しい説明を書く予定だった)
性能
ざっくりですが、エディタ上で見ると、10,000個のMonoBehaviour
よりは早いようです。しかし、これは皆さんご存じの通り、Update()を10000回呼ぶ問題です。
ちなみに、エディタ上でNativeArray
にアクセスする際にはチェックが範囲チェックが入ります。ビルドするとその辺が消えるらしいので、もっと早くなります。
また、ECSの強みはSOAによるキャッシュミスの削減ですので、呼び出し時間も大切ですが、キャッシュミスの削減が実現されているかどうかをチェックする必要があります。
ビルドし、Instruments.app
で測ってみます。上記の設定で測れているのかは確信がないのですが、L2キャッシュのリクエストのミスなのであってる?知ってる人がいたら教えてください。
SimpleECSの場合。
MonoBehaviourの場合。
type | min | max |
---|---|---|
SimpleECS | 21 | 62 |
MonoBehaviour | 57 | 89 |
び、微妙。
微妙ついでにL1D.REPLACEMENT
もカウントしてみます。
SimpleECS
MonoBehaviour
type | min | max |
---|---|---|
SimpleECS | 12 | 30 |
MonoBehaviour | 37 | 57 |
数値は減ってるな。うん。
まとめ
- ここまで一切触れてきてませんが、JobSystemは使っていません。なので、対応すればもっと早くなるはず。対応も難しくないはず
- 性能面よりも、コンポーネントを付けたりする際にどういうメモリレイアウトになってるのかを知りたかった
- そしてそれは果たされたし、自分で同じように実装してみて面倒くささがわかった
- SharedComponentとか本家にある便利機能は未実装
- unsafeなコードを書いていると、C++でも書いているんじゃないかという気分になってくる
[^1]: 最古は1998らしい https://en.wikipedia.org/wiki/Entity–component–system
[^3]: 全然関係ないですけど、公式的には"Entity Component System(通称ECS)"って書く決まりでもあるんですかね
[^2]: こういう場合は Graphics.DrawMeshInstancedIndirect()
すると良いっぽいですが、そこまでは気力がなかった