dskjal
広告
広告

Unity の Transform の読み書きは遅い

カテゴリ:unity

Unity のバージョンは 5.4 と 2017.2。実行環境は Pentium G3220・メモリ 10GB。

単純な方法では 10,000 個のオブジェクトの transform.position を更新するだけの処理(transform.position = newPos の実行)に約 14 ms かかった。60fps の上限が 16ms なので、単純な方法で 10,000 個の動くオブジェクトを出すのはほぼ不可能だ。

1フレームの更新にかかった時間の統計

1フレームの更新にかかった時間の統計

追記

Unity のバージョン 2017.2 で再計測したらパフォーマンスの改善が入っていた。バージョン 5.4 で約 14ms かかっていた 10,000 個のオブジェクトの position の更新処理は、2017.2 では約 3.5 ms になっている。これは約 4 倍のパフォーマンスの改善だ。

対処法

transform をキャッシュする

オブジェクトを大量に配置する場合、個々のオブジェクトの Update() で更新するのではなくマネージャクラスで更新処理をする。なぜなら Update() の呼び出し自体にオーバーヘッドがあるからだ。そのときトランスフォームをキャッシュしておくとパフォーマンスが向上する。

// 管理するオブジェクト

List<GameObject> objects = new List<GameObject>();

// 座標更新用 Transform のキャッシュ

List<Transform> transforms = new List<Transform>();

void Update(){

for(int i = 0; i < transforms.Count; ++i){

transforms[i].position = // 座標の更新

}

}

バージョン 2017.2 では 10,000 個のオブジェクトの更新処理に約 3.5 msかかるが、transform をキャッシュしておくと約 2.5 msになった。

10,000 個のオブジェクトの更新処理(再掲)

10,000 個のオブジェクトの更新処理(再掲)

rigidbody.position を使う

Rigidbody がついているなら、transform.position ではなく rigidbody.position を使う。Rigidbody は GetComponent<Rigidbody>() で取得する。これを実行すると約 14ms が 約 8ms になった。

rigidbody に変更した結果

rigidbody に変更した結果

ただし物理エンジンの更新処理は遅いので、全体のフレームレートはそれがない時より落ちる。つまり Rigidbody のついてないオブジェクトに Rigidbody をつけて高速化できるわけではない。

JobSystem を使う

JobSystem は 2018.1b8 から使える。b8 で 10,000 個の位置の更新に約 0.5ms かかった。ただしこれは 2018.1b8 でのローカル配列の位置更新と同じぐらいの時間だ。2コア2スレッドのマシンで単純な処理を JobSystem にしても効果はない。JobSystem の詳細なサンプルコードはLotteMakesStuff/SimplePhysicsDemo(GitHub)を参照。以下のコードは単純な動作テストだ。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.Profiling;

using Unity.Jobs;

using Unity.Collections;

public class jobsystem : MonoBehaviour {

private NativeArray<Vector3> positions;

public int objectCount = 10000;

struct SetPositionJob : IJobParallelFor{

public NativeArray<Vector3> positions;

public void Execute(int i){

positions[i] = Vector3.zero;

}

}

void Awake () {

positions = new NativeArray>Vector3>(objectCount, Allocator.Persistent);

}

void Update () {

Profiler.BeginSample("job");

var job = new SetPositionJob(){

positions = this.positions

};

var dependency = job.Schedule(objectCount, 32);

dependency.Complete();

Profiler.EndSample();

}

private void OnDisable(){

positions.Dispose();

}

}

JobSystem の実行結果

JobSystem の実行結果

ローカルの Position・Rotation を使う

Position・Rotation 用のメモリを自分で用意し、それを更新する。Transform は一切読み書きしない。レンダリングには Graphics.DrawMesh を使う。このときプレハブの MeshRenderer は無効にしておく。Graphics.DrawMesh 発行分の負荷は増えるが、遅い Transform にアクセスしないためトータルでのフレームレートは増加する。

ローカルのトランスフォームを使う

ローカルのトランスフォームを使う

約 14ms かかっていた位置更新が 約 1ms に短縮できた。ドローコールの発行に約 8ms かかっているので、トータルで 5ms の短縮になった。またトランスフォームの読み出し(次段を参照)も約 4ms から約 2ms へ短縮できている。

ComputeBuffer を使う

【Unity】Unite 2015「Rederer Massive Amount of Objects in Unity」レポートにあるように ComputeBuffer で更新することもできる。

読み出し

読み出しも書き込みほどではないが遅い。10,000 回の読み出し(var nowPos = transform.position の実行)に約 4ms かかっている。2回以上読みだされるならローカルにキャッシュしたほうがいい。

10,000 回の position の読み出し

10,000 回の position の読み出し

position VS. localPosition

子オブジェクトを持たないオブジェクトの更新速度は position でも localPosition でも変わらない。

10,000 個のオブジェクトの更新処理

10,000 個のオブジェクトの更新処理

ベンチスクリプト

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.Profiling;

public class NewBehaviourScript : MonoBehaviour {

int n = 10000;

List<GameObject> objects = new List<GameObject>();

List<Transform> transforms = new List<Transform>();

void Awake () {

for(int i = 0; i < n; ++i) {

var o = GameObject.CreatePrimitive(PrimitiveType.Cube);

objects.Add(o);

transforms.Add(o.transform);

}

}

void Update () {

var newPos = new Vector3();

Profiler.BeginSample("position");

for(int i = 0; i < n; ++i) {

objects[i].transform.position = newPos;

}

Profiler.EndSample();

Profiler.BeginSample("localposition");

for(int i = 0; i < n; ++i) {

objects[i].transform.localPosition = newPos;

}

Profiler.EndSample();

Profiler.BeginSample("transform cache");

for(int i = 0; i < n; ++i) {

transforms[i].position = newPos;

}

Profiler.EndSample();

}

}

関連記事

UPDATE()を10000回呼ぶ


広告
広告

カテゴリ