#内容
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セットとする。
- 読み込み
- 書き込み
(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は以下のとおり。
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をお勧め。