LoginSignup
23
17

More than 5 years have passed since last update.

Unityでいろいろなフォーマットのデシリアライズ速度を計測してみた

Last updated at Posted at 2015-12-24

いろいろなフォーマットのデシリアライズ速度を計測してみた

Unity5.3でJSONの公式サポートが実装されました!
https://unity3d.com/jp/unity/whats-new/unity-5.3
というわけでいろんなフォーマットはあるけれど簡単に実装したいときにどれが一番高速なの?やっぱり公式強いの?
と気になったので、計測してみました。

実験コードは↓になげておきました。
ご自由にお使いください
https://github.com/Marimoiro/UnityDeserializeTimes

方法 フォーマット 備考
Resources.Load ScriptableObject ロードのみ
JsonUtility Json Unity5.3から追加
JsonFx Json Jsonライブラリのひとつ
YamlDotNet Yaml Yamlパーサ
XmlSerializer Xml C#標準のxmlパーサ

結論としてはJsonUtilityとResources.Load使っとけってなりました

準備

対象データ

対象データは以下の二つです。
内部にクラス持ってると速度差でるかなーとか思って二種類用意しました。

SimpleParams.cs
using System;
using UnityEngine;
using System.Collections;

[Serializable]
public class SimpleParams
{ 
    public int X;

    public int XProperty
    {
        get { return X; }
        set { X = value; }
    }


    public double Y;
    public double YProperty
    {
        get { return Y; }
        set { Y = value; }
    }

    public string Message;

    public string MessageProperty
    {
        get { return Message; }
        set { Message = value; }
    }

    public override bool Equals(object obj)
    {
        var o = obj as SimpleParams;
        if (o == null) return false;
        return Equals(o);
    }

    protected bool Equals(SimpleParams other)
    {
        return X == other.X && Math.Abs(Y - other.Y) < 0.001 && string.Equals(Message, other.Message);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = X;
            hashCode = (hashCode*397) ^ (Message != null ? Message.Length : 0);
            return hashCode;
        }
    }
}

NestParams.cs
using System;
using UnityEngine;
using System.Collections;

[Serializable]
public class NestParams
{
    public SimpleParams Params;
    public SimpleParams SimpleProperty {get { return Params; }set { Params = value; } }

    public string Message;
    public string MessageProperty { get { return Message; } set { Message = value; } }

    public override bool Equals(object obj)
    {
        return Equals(obj as NestParams);
    }

    protected bool Equals(NestParams other)
    {
        if (other == null) return false;
        return Equals(Params, other.Params) && string.Equals(Message, other.Message);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return ((Params != null ? Params.GetHashCode() : 0)*397) ^ (Message != null ? Message.GetHashCode() : 0);
        }
    }
}


public class NestArray
{
    public NestParams[] NestParams;

    public NestParams[] NestParamsProperty
    {
        get { return NestParams; }
        set { NestParams = value; }
    }
}


注意点
1. Equalsなどは正しく読めているかのチェックのためReSharperちゃんが出してくれた適当なやつ(今考えたらAllでよかった)
2. プロパティを宣言しているのはYamlDotNetがフィールドをサポートしてないっぽいため

データ作成

こんなカスタムエディタ作って10000この配列にしました。

CreateAssetEditor.cs
using UnityEngine;
using System.Collections;
using System.Linq;
using UnityEditor;

public class CreateAssetEditor : MonoBehaviour
{

    const int Samples = 10000;
    [MenuItem("Assets/Create/SampleObjects")]
    static void CreateSampleAsset()
    {
        var simples = new int[Samples].Select(_ => new SimpleParams()
        {
            Message = "TestMessage",
            X = Random.Range(0, 10000),
            Y = Random.Range(0, 1f)
        }).ToArray();
        var nests = simples.Select(s => new NestParams() {Message = "NestParam", Params = s}).ToArray();

        var sa = ScriptableObject.CreateInstance<SimpleObject>();
        sa.SimpleParamses = simples;
        AssetDatabase.CreateAsset(sa,"Assets/Resources/simples.asset");

        var na = ScriptableObject.CreateInstance<NestObject>();
        na.NestParams = nests;
        AssetDatabase.CreateAsset(na, "Assets/Resources/nests.asset");

        AssetDatabase.SaveAssets();
    }
}

SimpleObject/NestObjectはSimpleParamsとNestParamsの配列を宣言してあるだけのオブジェクトです。
YamlDotNetが配列そのままシリアライズをサポートしてなかった…

計測方法

Cubeを作ってそこにぺたぺたパーサをはっていくかんじにしました。

パーサのベースクラス

IParser.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public interface IParser
{
    IList<int> SerializeTimeList { get; }
    IList<int> DeserializeTimeList { get; }

    string Serialize<T>(T obj);
    T Deserialize<T>(string text);
}

public class ParserBase : MonoBehaviour,IParser
{
    private readonly IList<int> serializeTimeList = new List<int>();
    private readonly IList<int> deserializeTimeList = new List<int>();

    public IList<int> SerializeTimeList
    {
        get { return serializeTimeList; }
    }

    public IList<int> DeserializeTimeList
    {
        get { return deserializeTimeList; }
    }

    public virtual string Serialize<T>(T obj)
    {
        throw new System.NotImplementedException();
    }

    public virtual T Deserialize<T>(string text)
    {
        throw new System.NotImplementedException();
    }
}

これを継承してたとえばUnityのJsonはこんな感じで時間を計ってます。
インスタンス生成やらの時間は考慮に入れないためにシリアライズ部分だけでの計測にしています。

UnityJson.cs
using System;
using UnityEngine;
using System.Collections;

public class UnityJsonParser : ParserBase {
    public override string Serialize<T>(T obj)
    {
        var s = Environment.TickCount;
        var r = JsonUtility.ToJson(obj);
        SerializeTimeList.Add(Environment.TickCount - s);
        return r;
    }

    public override T Deserialize<T>(string text)
    {
        var s = Environment.TickCount;
        var r = JsonUtility.FromJson<T>(text);
        DeserializeTimeList.Add(Environment.TickCount - s);
        return r;
    }
}

実際に計測するクラスはこんな感じで適当にScriptableObjectだけは大本だしそもそもデシリアライズできないしなので、別扱いしてます。

SamplingMaster.cs
using System;
using UnityEngine;
using System.Collections;
using System.Linq;

public class SamplingMaster : MonoBehaviour {

    // Use this for initialization
    void Start ()
    {
        //ScriptableObjectだけ
        var start = Environment.TickCount;
        var simples = new SimpleArray() {SimpleParams = Resources.Load<SimpleObject>("simples").SimpleParamses};
        var sl = Environment.TickCount - start;

        start = Environment.TickCount;
        var nest = new NestArray() {NestParams = Resources.Load<NestObject>("nests").NestParams};
        var nl = Environment.TickCount - start;
        OutputData(typeof (ScriptableObject), sl, nl, 0, 0);


        var parsers = GetComponents<IParser>();
        foreach (var parser in parsers)
        {
            var sText = parser.Serialize(simples);

            var nText = parser.Serialize(nest);

            var sObj = parser.Deserialize<SimpleArray>(sText);

            var nObj = parser.Deserialize<NestArray>(nText);

            OutputData(parser.GetType(), parser.DeserializeTimeList[0], parser.DeserializeTimeList[1], parser.SerializeTimeList[0], parser.SerializeTimeList[1]);
            ValidSimpleParams(simples,sObj);
            ValidNestParams(nest,nObj);
        }
    }

    void OutputData(Type parserType, int simpleLoadTime, int nestLoadTime, int simpleWriteTime, int nestWriteTime)
    {
        Debug.LogFormat("{0} \r\n シリアライズ\r\n  Simple:{1}ms \r\n  Nest:{2}ms \r\n デシリアライズ\r\n  Simple:{3}ms \r\n  Nest:{4}ms",
            parserType, simpleWriteTime, nestWriteTime, simpleLoadTime, nestLoadTime);
    }

    void ValidSimpleParams(SimpleArray expect,SimpleArray actual)
    {
        var err = expect.SimpleParams.Select((e, n) => new {E = e, A = actual.SimpleParams[n],N = n}).FirstOrDefault(p => !p.E.Equals(p.A));
        if (err == null)
        {
            Debug.Log("Valid OK[Simple]");
        }
        else
        {
            Debug.Log("Error");
        }
    }


    void ValidNestParams(NestArray expect, NestArray actual)
    {
        var err = expect.NestParams.Select((e, n) => new { E = e, A = actual.NestParams[n], N = n }).FirstOrDefault(p => !p.E.Equals(p.A));
        if (err == null)
        {
            Debug.Log("Valid OK[Nest]");
        }
        else
        {
            Debug.Log("Error");
        }
    }

}

結果

結果以下のようなデータになりました。(数値の単位はms)
すべてエディターでの計測です。

方法 Simpleデシリアライズ Nestデシリアライズ Simpleシリアライズ Nestシリアライズ
Resources.Load 16 47 - -
JsonUtility 16 31 16 15
JsonFx 1529 3616 500 1107
YamlDotNet 3026 4743 2730 4368
XmlSerializer 1389 3354 827 1935

参考までに別Windowsでやった場合を載せておきます(誰か追試してほしいな)

方法 Simpleデシリアライズ Nestデシリアライズ Simpleシリアライズ Nestシリアライズ
Resources.Load 32 31 - -
JsonUtility 15 32 0 16
JsonFx 1188 3125 500 1062
YamlDotNet 2766 4250 2438 3812
XmlSerializer 1109 2813 578 1406

所感

Resources.LoadはともかくJsonUtilityがべらぼうに速いです。
ResourcesLoadだけはファイルの読み込みもかかっているのでもう少しデシリアライズだけだと速いはず。(分けられないんですけどね)

JsonUtilityの制限はおそらくScriptableObjectと同じのようです。
なのでDictionaryなんかは使えないっぽい。

この結果だけ見ると固定データはScriptableObjectかJson,動的データはJsonにしてUnity公式のものを使うのがよいっぽい。

Yaml/Xmlはそれぞれデシリアライズの入力にTextReaderをとってstringを受け付けてくれない。その分のオーバーヘッドかかっているのかなぁというような気がします。
またResources.Loadは起動後最初のロードで若干のオーバーヘッドがあります、

23
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
17