Debug.Log() や Instantiate() などの速度を計測してみる

Unityでは重いと言われているメソッドがあります。他にも普段何気なく使っているメソッドなどが具体的にどのくらい重いのか知りたくなったので計測してみました。調べてみたのは以下のメソッドです。

 - Update()
 - GetComponent()
 - OnGUI()
 - Instantiate(),Destroy()
 - Debug.Log()

## ストップウォッチ
まずは計測用のストップウォッチを用意します。

```csharp:StopWatch.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;

public class StopWatch : MonoBehaviour {
    
    private int tick = 0;
    private List<int> timeList = new List<int>();

    void Update () {
        tick = Environment.TickCount;
    }
    
    IEnumerator Start()
    {
        while (true)
        {
            yield return new WaitForEndOfFrame();
            var delta = Environment.TickCount - tick;
            timeList.Add(delta);

            // サンプリングを10000回.その後ログ出力.
            if (timeList.Count == 9999)
            {
                Debug.Log("----- Average -----");
                Debug.Log(timeList.Average().ToString("0.00"));
                Debug.Break();
            }
        }
    }
}
```

公式ブログ記事で [UPDATE()を10000回呼ぶ](http://blogs.unity3d.com/jp/2015/12/23/1k-update-calls/) という記事があったのでそちらを参考にしました。
つまり、
1. Update でストップウォッチ起動(Script Execution Order で他のものより早く動作するようにする)
2. ストップウォッチ以外の処理がすべて行われる（今回の場合、空のコールバックなど）
3. yield WaitForEndFrame のタイミングでストップウォッチを停止
4. 上記を10000回実行後平均を出す
という感じです。
ただし、 LateUpdate() では [Unityライフサイクル](http://docs.unity3d.com/ja/current/Manual/ExecutionOrder.html) の関係で OnGUI() の計測ができないため yield WaitForEndOfFrame のタイミングでサンプリングを行うことにしました。そのため、正確な結果ではないかもしれません。PCによっても差はあるはずなので参考程度にしてください。

## オブジェクトの生成
次にオブジェクトを生成するスクリプトです。
基本は Awake() の時点で必要数（今回は1000個）生成し、Instantiate(), Destroy() の速度を計りたい時のみ Awake() をコメントアウトし、後半のコメントアウトを外す、というようにします。

```csharp:ObjectCreator.cs
using UnityEngine;

public class ObjectCreator : MonoBehaviour {

    [SerializeField] GameObject pref;

    void Awake()
    {
        for (int i = 0; i < 1000; i++)
        {
            Instantiate(pref);
        }
    }

    //void Update()
    //{
    //    for (int i = 0; i < 1000; i++)
    //    {
    //        var obj = Instantiate(pref);
    //        Destroy(obj);
    //    }
    //}
}
```
また、 Instantiate() と Destroy() は Instantiate() ばかりだとメソッドの呼び出しというよりオブジェクトの数で速度が落ち、 Destroy() は単体では使用できないためセットで計測しました。

## 各種計測用スクリプト

至ってシンプルな内容（というか何もない）です。

```csharp:UpdateTest.cs
using UnityEngine;

public class UpdateTest : MonoBehaviour {
	void Update () { }
}
```

```csharp:OnGUI.cs
using UnityEngine;

public class OnGUITest : MonoBehaviour {
	void OnGUI() { }
}
```

```csharp:GetComponentTest.cs
using UnityEngine;

public class GetComponentTest : MonoBehaviour {

	void Update () {
        GetComponent<UpdateTest>();
	}
}
```

```csharp:DebugLogTest.cs
using UnityEngine;

public class DebugLogTest : MonoBehaviour {
	
	void Update () {
        Debug.Log("");	
	}
}
```
## 計測結果

さて、計測結果です。
空のゲームオブジェクトを1000個出しただけのもの（実質処理時間 0 ）も計測してみました。

| 計測対象 | 処理時間 |
|:-----------|------------:|
| 空                      |        4.24ms |
| Update()                |      4.52ms |
| OnGUI()                 |        13.03ms |
| GetComponent() - 対象コンポーネントがある場合 |          5.05ms |
| GetComponent() - 対象コンポーネントがない場合 | 8.19ms
| Instantiate(), Destroy()|       33.15ms|
| Debug.Log()             |     1110.18ms|
お分かり頂けただろうか…。
Update()などのコールバックを書いているだけでも少し重くなるんですよー。OnGUI()はもっとやばいんですよー。Instantiate()Destroy()はやばいからオブジェクトプーリングしようね、みたいなことを軽く伝えようと思っていたのですが、すべてを吹き飛ばす Debug.Log() の存在感…。

Debug.Log() 重すぎる上に、動作していると実機でもログが取れてしまうのでリリース時には[この記事](http://qiita.com/toRisouP/items/d856d65dcc44916c487d)のように必ず消すようにしましょう。

GetComponent() は対象がいる場合、いない場合でかなり結果に差が出たのも気になる点です。
これはまた別の機会に調べてみたいと思います。