先日Debug.Log() や Instantiate() などの速度を計測してみるという記事で普段使っているメソッドの速度を計測してみましたが、その中で1つ気になることがありました。それはGetComponent() は対象がいる場合といない場合で速度が違っていたことです。
そこで今回は GetComponent() に絞って検証してみました。
仮説はインスペクターでの表示順が GetComponent() での取得順に影響するというものです。
つまり、取得したいコンポーネントが TargetComponent クラスだった場合、これが上のほうにあればあるほど速度が増すのではないか、ということです。
ストップウォッチ
先日のようにOnGUI()を計測するわけではないので Update(), LateUpdate() を用いたストップウォッチにしました。
これで出力結果は純粋なメソッドの処理時間の平均になります。
他には特に特筆することはありませんが、一応コードを載せておきます。
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System;
public class StopWatch : MonoBehaviour {
private int tick = 0;
private List<int> tickList = new List<int>();
void Update () {
tick = Environment.TickCount;
}
void LateUpdate()
{
var delta = Environment.TickCount - tick;
tickList.Add(delta);
// サンプリングを10000回.その後ログ出力.
if(tickList.Count == 9999)
{
Debug.Log("----- Average -----");
Debug.Log(tickList.Average().ToString("###.00"));
Debug.Break();
}
}
}
テスト内容
テスト内容は以下のように設定しました。
・オブジェクトを1000個インスタンス化。それぞれのオブジェクトに GetComponentTest.cs を1つアタッチ
・すべてのオブジェクトに対してコンポーネントを1000個アタッチする。
その際、GetComponent() 対象をアタッチするか、何番目にアタッチするかを設定できるようにする。
・アタッチするコンポーネントの中身は空にする ( TargetComponent.cs, DummyComponent.cs )
・ストップウォッチで10000回計測し、平均を出す
using UnityEngine;
public class TestManager : MonoBehaviour {
[SerializeField]
private GameObject pref;
[SerializeField, ReadOnly]
private int testObjCount = 1000; // オブジェクト数.
private const int compCount = 1000; // Addするコンポーネント数.
[SerializeField]
private bool addTargetComponent = true; // GetComponent対象をAddするか
[SerializeField, Range(0, compCount)]
private int addIndex; // 何番目に対象コンポーネントをAddするか.
void Awake()
{
for (int i = 0; i < testObjCount; i++)
{
var go = Instantiate(pref) as GameObject;
go.AddComponent<GetComponentTest>();
for (int j = 0; j < compCount; j++)
{
AddTestComponent(go, j);
}
}
}
void AddTestComponent(GameObject go, int index)
{
if (addTargetComponent &&
index == addIndex)
{
go.AddComponent<TargetComponent>();
}
else
{
go.AddComponent<DummyComponent>();
}
}
}
using UnityEngine;
public class GetComponentTest : MonoBehaviour {
void Update () {
GetComponent<TargetComponent>();
}
}
using UnityEngine;
public class TargetComponent : MonoBehaviour { }
using UnityEngine;
public class DummyComponent : MonoBehaviour { }
結果
以下が計測結果です。
若干対象をアタッチしない方が1000番目(最後)にアタッチした場合より早くなっていますが、これはちょっとした揺らぎだと思われるので無視していいかと。
計測対象 | 処理時間 |
---|---|
対象を1番目にアタッチする | 1.58ms |
対象を5番目にアタッチする | 1.82ms |
対象を100番目にアタッチする | 4.61ms |
対象を1000番目にアタッチする | 32.55ms |
対象をアタッチしない | 32.54ms |
以上の結果を鑑みると、 GetComponent() は対象オブジェクトにコンポーネントがアタッチされているか、頭から総当たりで探すメソッドである、ということが分かります。~~100個以上アタッチとかありえないと思うけど。~~対象がない場合が全体検索に匹敵するのは見つかるまで調べているからだった、ということですね。
前にぼくのブログでチラッと書いたGetComponentのイメージは当たってたということになります。やったぜ。
ともあれ、頻繁にアクセスするコンポーネントは上の方に配置するように意識するといいかもしれませんね。