LoginSignup
5
7

More than 5 years have passed since last update.

[Unity] 永続化シリアライザー比較 Easy Save vs Flatbuffers

Last updated at Posted at 2016-10-31

内容

UnityのAssetStoreで売られているEasy SaveとGoogle謹製Flatbuffersの両シリアライザーを比較検討する。
比較内容はゲーム内のデータを保存して次回以降でも正しく読めるかというもの。

EasySave:
https://www.assetstore.unity3d.com/jp/#!/content/768

FlatBuffers:
https://github.com/google/flatbuffers
https://github.com/google/flatbuffers/releases
https://google.github.io/flatbuffers/index.html

環境

Unity 5.4.1p4
Android 4.0.4
ビルドはIL2CPPを使用。

Flatbuffers v1.4.0 (ランタイムは2016/11/1 最新のもの)
Easy Save2 v2.7.1f2

結果

Android 4.0.4の低スペック実機での確認結果。
FlatBuffersの方が1/2くらいの速度で実行できることが分かった。

値はmilliseconds.
11回目~20回目はコルーチンを呼ぶ順番を変えて実行。

EasySave2 FlatBuffers
1 2,749 1,246
2 2,800 1,329
3 2,752 1,266
4 2,741 1,226
5 2,746 1,242
6 2,723 1,217
7 2,726 1,236
8 2,716 1,231
9 2,683 1,242
10 2,670 1,218
11 3,131 1,249
12 2,571 1,223
13 2,573 1,220
14 2,512 1,215
15 2,598 1,246
16 2,528 1,220
17 2,591 1,235
18 2,627 1,253
19 2,558 1,243
20 2,513 1,220
Average 2,675.4 1,238.9
Max 3,131 1,329
Min 2,512 1,215
EasySaveとの比較
Average 1.00 0.46
Max 1.00 0.42
Min 1.00 0.48
データサイズ 103[bytes] 60[bytes]

実験内容

実験の内容は自分が使うにあたっての調査なので
公平性があるものではありません。あしからず。

設定データを保存して読み込むことを想定する。
保存データは

  • Version (int)
  • Music Volume (float)
  • Effects Volume (float)
  • Language (int)

とする。

読み込みを最初に実行して、その後書き込み、これを1セットとする。

  1. 読み込み
  2. 書き込み

(IN -> OUT -> IN -> OUT -> ...という繰り返し)

ファイルに書き込まれたバージョンによって
読み込み処理を変更することを想定して
バージョンで分岐処理を入れる。

Easy Save2は有料アセットなのでソースコートは公開できません。

Easy Save2の実装

Easy Save2に構造化データを保存しようとしたとき下記の実装が考えられる。
Tags: Saving Multiple Variables to One File

ただ上記実装では値を文字列への変換、文字列をパース、パースした結果を保存と
考えるだけ遅いので、以下のReader, Wirterを使用する。
(Faster Saving and Loading using ES2Writer and ES2Reader)[http://docs.moodkie.com/easy-save-2/guides/faster-saving-and-loading-using-es2writer-and-es2reader/]

EaseSaveが保存したファイルをみたが独自バイナリフォーマットっぽかった。
そういう意味ではFlatBuffersと変わらない。

FlatBuffersの実装

前回の実験内容を踏まえての実装をする。

IDLは以下のとおり。

Test.fbs
namespace Data;

file_identifier "MYFI";

union Data
{
    SettingsDataV1,
}

table Root
{
    data:Data;
}

table SettingsDataV1
{
    version:int;
    music_volume:float;
    effects_volume:float;
    language:int;
}

root_type Root;

これをC#のスクリプトに出力して使用した。

テストコード

using FlatBuffers;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Assertions;
using System.Collections;
using System.Diagnostics;
using System.IO;

public class Serializer : MonoBehaviour
{
    const int COUNT = 1000;
    const string EASY_SAVE_FILE_NAME = "EasySave.txt";
    const string EASY_SAVE_TAG_VERSION = "Version";
    const string EASY_SAVE_TAG_MUSIC_VOLUME = "MusicVolume";
    const string EASY_SAVE_TAG_EFFECTS_VOLUME = "EffectsVolume";
    const string EASY_SAVE_TAG_LANGUAGE = "Language";

    [SerializeField]
    Text _textEasySave;

    [SerializeField]
    Text _textFlatBuffers;

    long _easySaveElapsedTime;
    long _flatbuffresElapsedTime;

    IEnumerator Start()
    {
        System.GC.Collect();
        yield return StartCoroutine(FlatBuffers());
        System.GC.Collect();
        yield return StartCoroutine(EasySave());

        _textEasySave.text = string.Format("EasySave : {0}[ms]", _easySaveElapsedTime);
        _textFlatBuffers.text = string.Format("FlatBuffers : {0}[ms]", _flatbuffresElapsedTime);
    }

    IEnumerator EasySave()
    {
        Stopwatch stopWatch = new Stopwatch();

        int version = 0;
        float musicVolume = 0f;
        float effectsVolume = 0f;
        int language = 0;

        if (ES2.Exists(EASY_SAVE_FILE_NAME))
            ES2.Delete(EASY_SAVE_FILE_NAME);

        ES2Settings settings = new ES2Settings();
        settings.optimizeMode = ES2Settings.OptimizeMode.Fast;

        stopWatch.Start();

        for (int i = 0; i < COUNT; ++i)
        {
            if (ES2.Exists(EASY_SAVE_FILE_NAME))
            {
                using (ES2Reader reader = ES2Reader.Create(EASY_SAVE_FILE_NAME, settings))
                {
                    version = reader.Read<int>(EASY_SAVE_TAG_VERSION);

                    switch(version)
                    {
                        default:
                            language = reader.Read<int>(EASY_SAVE_TAG_LANGUAGE);
                            break;
                    }
                }
            }

            version++;
            musicVolume += 1f;
            effectsVolume += 1f;
            language++;

            using (ES2Writer writer = ES2Writer.Create(EASY_SAVE_FILE_NAME, settings))
            {
                writer.Write(version, EASY_SAVE_TAG_VERSION);
                writer.Write(musicVolume, EASY_SAVE_TAG_MUSIC_VOLUME);
                writer.Write(effectsVolume, EASY_SAVE_TAG_EFFECTS_VOLUME);
                writer.Write(language, EASY_SAVE_TAG_LANGUAGE);
                writer.Save();
            }

            version = 0;
        }

        stopWatch.Stop();
        _easySaveElapsedTime = stopWatch.ElapsedMilliseconds;

        yield return null;
    }

    IEnumerator FlatBuffers()
    {
        Stopwatch stopWatch = new Stopwatch();

        int version = 0;
        float musicVolume = 0f;
        float effectsVolume = 0f;
        int language = 0;

        string path = Path.Combine(Application.persistentDataPath, EASY_SAVE_FILE_NAME);

        if (File.Exists(path))
            File.Delete(path);

        stopWatch.Start();

        for (int i=0; i<COUNT; ++i)
        {
            // 読み込み
            if(File.Exists(path))
            {
                ByteBuffer buffer = new ByteBuffer(File.ReadAllBytes(path));

                Data.Root root = Data.Root.GetRootAsRoot(buffer);

                Data.Data dataType = root.DataType;

                switch (dataType)
                {
                    case Data.Data.SettingsDataV1:
                        Data.SettingsDataV1 settings = root.GetData(new Data.SettingsDataV1());
                        version = settings.Version;
                        musicVolume = settings.MusicVolume;
                        effectsVolume = settings.EffectsVolume;
                        language = settings.Language;
                        break;
                }
            }

            version++;
            musicVolume += 1f;
            effectsVolume += 1f;
            language++;

            // 書き込み
            {
                FlatBufferBuilder builder = new FlatBufferBuilder(1);

                int offestData;
                Data.Data dataType;

                Offset<Data.SettingsDataV1> data = Data.SettingsDataV1.CreateSettingsDataV1(builder, version, musicVolume, effectsVolume, language);
                offestData = data.Value;
                dataType = Data.Data.SettingsDataV1;

                Data.Root.StartRoot(builder);
                Data.Root.AddDataType(builder, dataType);
                Data.Root.AddData(builder, offestData);
                Offset<Data.Root> endOffset = Data.Root.EndRoot(builder);

                Data.Root.FinishRootBuffer(builder, endOffset);

                //byte[] bytes = builder.SizedByteArray();

                //File.WriteAllBytes(path, bytes);

                using (MemoryStream ms = new MemoryStream(builder.DataBuffer.Data, builder.DataBuffer.Position, builder.Offset))
                {
                    byte[] bytes = ms.ToArray();
                    File.WriteAllBytes(path, bytes);
                }
            }

            version = 0;
        }

        stopWatch.Stop();
        _flatbuffresElapsedTime = stopWatch.ElapsedMilliseconds;

        yield return null;
    }
}

このスクリプトをGameObjectへアタッチして
uGUIのテキストへ結果を出力して確認した。

感想

EasySaveはその名の通り簡単に保存ができるところがポイント。
速度重視でなければ、とくにEasySaveでも問題なさそう。

FlatBuffersはIDLからコード出力やデータの書き込み、読み込みの実装がやや複雑。
速度重視ならばFlatBuffersをお勧め。

5
7
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
5
7