Unity
FlatBuffers
EasySave

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

More than 1 year has passed since last update.


内容

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をお勧め。