エンジニア

Unity C# プロパティのキャッシュによる速度の違い

投稿日:2015年11月5日 更新日:

今回のエンジニアブログを担当する加賀です。

UnityのGameObjectで、よく読み書きするプロパティは「transform」だと思います。
また、Unityに限らずインスタンスを1つだけに限定する時にシングルトンをよく使います。

これらを、インスタンスにアクセスする度に毎回プロパティを取得する場合と、
処理の最初に1度インスタンスを取得して、取得したインスタンスにアクセスする場合とで
どれほど処理時間の差があるかを計測してみました。

計測環境

  • Mac OS X 10.9.5
  • Intel Core i5 1.3GHz
  • Unity 5.2.2

 

基本コード

100回計算して、最小と最大の値を除いた98回の平均値を時間とします。
なお、生成される乱数の順序を同じにするために、seed値を指定しています。
以下のCalcTimeメソッドを変えて計測してみます。

public class Test : MonoBehaviour {
  bool isFinish;

  void Start() {
    isFinish = false;
    UnityEngine.Random.seed = 12345;
  }

  void Update() {
    if (!isFinish) {
      List<int> time = new List<int> ();
      for (int i = 0; i < 100; i++) {
        DateTime start = DateTime.Now;
        CalcTime ();
        DateTime end = DateTime.Now;
        TimeSpan span = end - start;
        time.Add (span.Milliseconds);
      }
      int min = time.Min ();
      time.Remove (min);
      int max = time.Max ();
      time.Remove (max);
      double avg = time.Average ();
      Debug.Log ("min:" + min + " max:" + max + " avg:" + avg);
      isFinish = true;
    }
  }

  // ここから  

  void CalcTime() {
  }

  // ここまで変えます  
}

transform

CalcTimeメソッド内で、50000フレーム分の処理を行います。
1フレーム内で、右(X+)方向へ移動し、サイズを拡大させ、Y軸回転させます。

transform(毎回プロパティ経由)

void CalcTime() {
  // 初期化  
  transform.localPosition = Vector3.zero;
  transform.localScale    = Vector3.one;
  transform.localRotation = Quaternion.identity;

  // 計測  
  for (int i = 0; i < 50000; i++) {
    transform.localPosition = transform.localPosition + Vector3.right;
    transform.localScale    = transform.localScale + Vector3.one;
    transform.localRotation = transform.localRotation * Quaternion.Euler (Vector3.up);
  }
}
transform(最初にキャッシュ)

void CalcTime() {
  // キャッシュ  
  Transform transformCache = transform;

  // 初期化  
  transformCache.localPosition = Vector3.zero;
  transformCache.localScale    = Vector3.one;
  transformCache.localRotation = Quaternion.identity;

  // 計測  
  for (int i = 0; i < 50000; i++) {
    transformCache.localPosition = transformCache.localPosition + Vector3.right;
    transformCache.localScale    = transformCache.localScale + Vector3.one;
    transformCache.localRotation = transformCache.localRotation * Quaternion.Euler (Vector3.up);
  }
}
測定結果(transform)

処理方法 結果(ミリ秒) 5回平均(ミリ秒)
毎回プロパティ経由 毎回プロパティ経由の結果 54.01428571428572
最初にキャッシュ 最初にキャッシュの結果 42.4469387755102

シングルトン

以下のシングルトンクラスを使用します。
0〜9,999までのキー、0〜999までの整数の組み合わせをインスタンス生成時に作成します。
CalcTimeメソッド内で、シングルトンクラスにある取得メソッドを、100,000回ランダムな0〜9,999までの整数で呼び出します。

public class TestSingleton {
  // シングルトン  
  private TestSingleton instance = null;
  public TestSingleton Instance {
    get {
      if (instance == null) {
        instance = new TestSingleton ();
      }
      return instance;
    }
  }

  Dictionary<int, int> dict;
  void TestSingleton() {
    dict = new Dictionary<int, int> ();
    for (int i = 0; i < 10000; i++) {
      dict [i] = UnityEngine.Random.Range (0, 1000);
    }
  }

  public int Get(int val) {
    return dict [val];
  }
}
シングルトン(毎回プロパティ経由)

// 計測前に初期処理を終わらせる(ここにアクセスしない)  
TestSingleton singletonInstance = TestSingleton.Instance;

void CalcTime() {
  int sum = 0;
  for (int i = 0; i < 100000; i++) {
    sum += TestSingleton.Instance.Get (UnityEngine.Random.Range (0, 10000));
  }
}
シングルトン(最初にキャッシュ)

// 計測前に初期処理を終わらせる(ここにアクセスしない)  
TestSingleton singletonInstance = TestSingleton.Instance;

void CalcTime() {
  // キャッシュ  
  TestSingleton instance = TestSingleton.Instance;

  int sum = 0;
  for (int i = 0; i < 100000; i++) {
    sum += instance.Get (UnityEngine.Random.Range (0, 10000));
  }
}
測定結果(シングルトン)

処理方法 結果(ミリ秒) 5回平均(ミリ秒)
毎回プロパティ経由 毎回プロパティ経由の結果 16.6
最初にキャッシュ 最初にキャッシュの結果 13.47959183673472

結果から

transform、シングルトンのどちらもちょっとした実行時間短縮になりましたが、そこまで大きくは変化しませんでした。
しっかりボトルネックに対処することが前提になりますが、処理の遅い端末でも快適に動かそうとするなら、
このちょっとした実行時間短縮が大事になると思います。

ただ、塵も積もればの精神で、ちょっとした積み重ねが後々生きてくると思いますので、
コードが見づらくならないくらいにキャッシュすることを実行していこうと思います。

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.