広告
広告

Unity の Transform の読み書きは遅い

カテゴリ:unity

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

position V.S. localPosition

position ではなく localPosition を使えば早いのでは?という指摘があったのでテストしてみたが変わらなかった. 子オブジェクトを持たないオブジェクトの更新速度は position でも localPosition でも変わらない.

pos vs localPos
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();
    }
}

古い記事

以下のベンチの結果は古いので参考にする必要はないが,対処法自体は今でも有効だ.

Unity のバージョンは 5.4.

Pentium G3220 のマシンで 10,000 個のオブジェクトの transform.position を更新するだけの処理(transform.position = newPos の実行)に約 14 ms かかっている. 60fps の上限が 16ms なので,Unity で 10,000 個の動くオブジェクトを出すのはほぼ不可能だ.

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

対処法

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になった.

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

rigidbody.position を使う

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

rigidbody
rigidbody に変更した結果

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

ローカルの Position・Rotation を使う

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

local 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回以上読みだされるならローカルにキャッシュしたほうがいい.

read pos
10,000 回の position の読み出し

関連記事

UPDATE()を10000回呼ぶ


広告
広告