(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

enumをkeyにしたDictionaryが遅いって本当ですか?【C#】【Unity】【最適化】


このエントリーをはてなブックマークに追加

この記事でのバージョン
Unity 5.4.0f3


はじめに

c#のenumは遅いという話を耳にしたので、気になって調べてみた系の記事です!


そもそものc#のenumは遅いという由来は以下の通り。

enumは糖衣構文です。実態はclass Enumです。


糖衣構文……?知らぬ存ぜぬ。

という事でwikipediaから抜粋。

糖衣構文(とういこうぶん、syntactic sugar)は、プログラミング言語において、読み書きのしやすさのために導入される書き方であり、複雑でわかりにくい書き方と全く同じ意味になるものを、よりシンプルでわかりやすい書き方で書くことができるもののことである。

糖衣構文 - Wikipedia


遅くなる条件

C#のenumが糖衣構文(シンタックスシュガー)という事は分かりましたが、

重要なのは、どんな時に遅くなるかということですね。


先ほどの記事によるとDictionaryのkeyにした時らしいです。

このEnumはenumの本分を尽くす限りにおいては高速です。
Dictionary のTKeyにenumを使うと低速で、TKeyをintにしておいてintにキャストしたenumを使うと高速ということです。


ということで実際に計測してみました!


計測

計測したコードは以下の通りで、計測には以前紹介したProcessTimerを使用しました。

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

public class EnumTest : MonoBehaviour {

  //計測用のenum
  private enum EnumKey{
    Key1 = 0, Key2, Key3, Key4
  }

  //計測用のDict
  private Dictionary<EnumKey, int> _enumDict = new Dictionary<EnumKey, int> (){
    {EnumKey.Key1, 1}, {EnumKey.Key2, 2}, {EnumKey.Key3, 3}, {EnumKey.Key4, 4}
  };
  private Dictionary<int, int> _intDict = new Dictionary<int, int> (){
    {0, 1}, {1, 2}, {2, 3}, {3, 4}
  };

  private void Start () {
    Measure ();
  }

  /// <summary>
  /// 計測を行う
  /// </summary>
  public void Measure(){
    int loopCount = 1000000;

    //両者の処理を計測しログに出力
    Debug.Log ("EnumAction : "      + ProcessTimer.MeasureAction(EnumAction,       loopCount));
    Debug.Log ("IntAction : "       + ProcessTimer.MeasureAction(IntAction ,       loopCount));
    Debug.Log ("EnumToIntAction : " + ProcessTimer.MeasureAction(EnumToIntAction , loopCount));
  }

  //keyをenumにした場合の処理
  private void EnumAction(){
    int no = _enumDict [EnumKey.Key1];
    no = _enumDict [EnumKey.Key2];
    no = _enumDict [EnumKey.Key3];
    no = _enumDict [EnumKey.Key4];
  }

  //keyをintにした場合の処理
  private void IntAction(){
    int no = _intDict [0];
    no = _intDict [1];
    no = _intDict [2];
    no = _intDict [3];
  }

  //Dictのkeyはint、入力はenumをintにした場合の処理
  private void EnumToIntAction(){
    int no = _intDict [(int)EnumKey.Key1];
    no = _intDict [(int)EnumKey.Key2];
    no = _intDict [(int)EnumKey.Key3];
    no = _intDict [(int)EnumKey.Key4];
  }

}


ProcessTimerについては以下の記事を参照のこと。



詳細の説明は省きますが、

データを100万*4回取り出し時の処理時間を計測、ログで出力しています。


比較対象は、

  1. enumをkeyにした場合
  2. intをkeyにした場合
  3. intをkeyにしたdictにintにキャストしたenumを使って取り出した場合

の3パターンです。


コードで表すと以下のような感じ。

//enumをkeyにした場合
_enumDict [EnumKey.Key1];

//intをkeyにした場合
_intDict [0]; 

//intをkeyにしたdictにintにキャストしたenumを使って取り出した場合
_intDict [(int)EnumKey.Key1];


気になる結果ですが以下の通りでした。


f:id:kan_kikuchi:20160914134657p:plain

1のパターン 1.589827秒
2のパターン 0.243197秒
3のパターン 0.242863秒


enumをkeyにしたDictionaryが遅いって本当ですね!


とは言え、一気に400万回実行することもそうそうないでしょうという事で、

処理回数を減らして比較してみました。


なお、2と3はほぼ処理時間が同じなので、1と2のパターンだけで比較しています。

40万回
enum:0.139290秒
int:0.023423秒

4万回
enum:0.018815秒
int:0.002139秒

4000回
enum:0.000978秒
int:0.000208秒


4000回の時点でほぼ誤差のような値なので、

よっぽどの事がない限り気にしなくていいような感じですかね。


とは言え、遅いと知っていて使うのと、

知らずに使うでは、だいぶ違うと思うので良い勉強になりました。


実機

と、終わる予定でしたが、

よくよく考えて見ると我らが使っているのはUnityです。


そう、iPhoneなどの実機に書き出す時はIL2CPPを使って書き出しているので、

C#じゃなくてC++で動作しているんですよね?(IL2CPPをよくわかってないマン)


ちなみにIL2CPPとは以下のようなものらしいです。

IL2CPP スクリプティングバックエンドは Unity プロジェクトにあるスクリプト、アセンブリからなる IL コードをC++ コードに変換します。


もし、IL2CPPがenum部分をいい感じに変換してくれていたら、早くなるのでは!

という事で、先ほどのそれぞれ400万回実行するコードをiPhone6s+で動かしてみました!

EnumAction : 1.104017
IntAction : 0.109976
EnumToIntAction : 0.110532


実機でも遅かったよ……(´・ω・`)