#内容
ローカルにデータを保存するときにZeroFormatterとFlatBuffersのどちらが速いか比較検討する。
この手のテストは書き込むデータタイプによって偏りがでるので公平性は無視して
ある程度、普段使いしそうなデータを書き込む。
(I/Oの速度もあるしテストする機によってもアレだしね )
データタイプは前回使ったデータタイプを使用する。
#環境
Unity 5.4.1p1
Windows10 (ビルド)
IL2CPP
実行はAndroid4.0.4の低スペック機。(前回EasySaveで比較した端末と同じ)
FlatBuffers v1.4.0 (ランタイムは2016/11/1 最新のもの)
ZeroFormatter v1.0.1
#結果
まったく同じですね。
コードはgithubにあるのでシリアライズするデータを変更したり
各自の端末でテストして比較してもらえればと思います。
11回目からは関数のコール順を変更。
ZeroFormatter | FlatBuffers | |
---|---|---|
1 | 566 | 669 |
2 | 530 | 449 |
3 | 508 | 446 |
4 | 501 | 446 |
5 | 521 | 454 |
6 | 494 | 456 |
7 | 462 | 441 |
8 | 523 | 450 |
9 | 496 | 453 |
10 | 491 | 453 |
11 | 464 | 570 |
12 | 474 | 545 |
13 | 479 | 496 |
14 | 482 | 493 |
15 | 463 | 448 |
16 | 482 | 475 |
17 | 460 | 462 |
18 | 471 | 453 |
19 | 484 | 562 |
20 | 464 | 505 |
Average | 490.8 | 486.3 |
Max | 566 | 669 |
Min | 460 | 441 |
0 | 0 | 0 |
EasySaveとの比較 | ||
Average | 1.00 | 0.99 |
Max | 1.00 | 1.18 |
Min | 1.00 | 0.96 |
ちなみにエディタ上で実行したときにできたファイルサイズは
どちらも84[byte]でした
#実験内容
前回と同じgithubのリポジトリに収録している。
https://github.com/akerusoft/UnityTestZeroFormatter
using FlatBuffers;
using UnityEngine;
using UnityEngine.UI;
using System.Diagnostics;
using System.Collections;
using System.IO;
using ZeroFormatter;
namespace Test2
{
public class GameController : MonoBehaviour
{
const int COUNT = 1000;
const string ZERO_FORMATTER_FILE_NAME = "ZeroFormatter.bin";
const string FLAT_BUFFERS_FILE_NAME = "FlatBuffers.bin";
const string MONSTER_NAME = "スライム";
[SerializeField]
Text _textFlatBuffers;
[SerializeField]
Text _textZeroFormatter;
long _flatbuffresElapsedTime;
long _zeroFormatterElapsedTime;
IEnumerator Start()
{
System.GC.Collect();
Resources.UnloadUnusedAssets();
yield return null;
TestFlatBuffers();
System.GC.Collect();
Resources.UnloadUnusedAssets();
yield return null;
TestZeroFormatter();
_textFlatBuffers.text = string.Format("FlatBuffers:{0}[ms]", _flatbuffresElapsedTime);
_textZeroFormatter.text = string.Format("ZeroFormatter:{0}[ms]", _zeroFormatterElapsedTime);
}
void TestFlatBuffers()
{
Stopwatch stopWatch = new Stopwatch();
string name = null;
uint hitPoint = 0;
float hitRate = 0f;
uint speed = 0;
uint luck = 0;
string path = Path.Combine(Application.persistentDataPath, FLAT_BUFFERS_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));
MyFlatBuffers.Root root = MyFlatBuffers.Root.GetRootAsRoot(buffer);
MyFlatBuffers.Data dataType = root.DataType;
switch (dataType)
{
case MyFlatBuffers.Data.MonsterDataV1:
MyFlatBuffers.MonsterDataV1 monsterData = root.GetData(new MyFlatBuffers.MonsterDataV1());
name = monsterData.Name;
hitPoint = monsterData.Hp;
hitRate = monsterData.HitRate;
speed = monsterData.Speed;
luck = monsterData.Luck;
break;
}
}
hitPoint++;
hitRate += 1f;
speed++;
luck++;
// 書き込み
{
FlatBufferBuilder builder = new FlatBufferBuilder(1);
int offestData;
MyFlatBuffers.Data dataType;
Offset<MyFlatBuffers.MonsterDataV1> data = MyFlatBuffers.MonsterDataV1.CreateMonsterDataV1(builder, builder.CreateString(MONSTER_NAME), hitPoint, hitRate, speed, luck);
offestData = data.Value;
dataType = MyFlatBuffers.Data.MonsterDataV1;
MyFlatBuffers.Root.StartRoot(builder);
MyFlatBuffers.Root.AddDataType(builder, dataType);
MyFlatBuffers.Root.AddData(builder, offestData);
Offset<MyFlatBuffers.Root> endOffset = MyFlatBuffers.Root.EndRoot(builder);
MyFlatBuffers.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);
}
}
hitPoint = 0;
}
stopWatch.Stop();
_flatbuffresElapsedTime = stopWatch.ElapsedMilliseconds;
}
void TestZeroFormatter()
{
Stopwatch stopWatch = new Stopwatch();
string name = null;
uint hitPoint = 0;
float hitRate = 0f;
uint speed = 0;
uint luck = 0;
string path = Path.Combine(Application.persistentDataPath, ZERO_FORMATTER_FILE_NAME);
if (File.Exists(path))
File.Delete(path);
stopWatch.Start();
for (int i = 0; i < COUNT; ++i)
{
// 読み込み
if (File.Exists(path))
{
DataRoot dataRoot = ZeroFormatterSerializer.Deserialize<DataRoot>(File.ReadAllBytes(path));
MonsterDataBase dataBase = dataRoot.LoadMonsterData();
if (dataBase is MonsterDataV1)
{
MonsterDataV1 data = dataBase as MonsterDataV1;
name = data.Name;
hitPoint = data.HitPoint;
hitRate = data.HitRate;
speed = data.Speed;
luck = data.Luck;
}
}
hitPoint++;
hitRate += 1f;
speed++;
luck++;
// 書き込み
{
MonsterDataV1 data = new MonsterDataV1()
{
Name = MONSTER_NAME,
HitPoint = hitPoint,
HitRate = hitRate,
Speed = speed,
Luck = luck,
};
DataRoot dataRoot = new DataRoot();
dataRoot.SetMonsterData(data);
File.WriteAllBytes(path, ZeroFormatterSerializer.Serialize(dataRoot));
}
hitPoint = 0;
}
stopWatch.Stop();
_zeroFormatterElapsedTime = stopWatch.ElapsedMilliseconds;
}
}
}
#手順
アプリを起動して落として、また起動してって繰り返すだけ。
#感想
今回書き込んだデータは速度面が全く変わらないと言っていいレベル。
FlatBuffersは実装方法を忘れがち。
前のコードみてあーだったなーとかっていうレベル。
ZeroFormatterはインスタンスを渡すだけというお手軽設計。
どちらもC#コードを自動生成するということはかわらなくても
実装面や使いやすさからすると圧倒的にZeroFormatterがよい。
ソースコードが1行で済むけど速度が遅くなるなら
コード量は増えてもいいと思っているけど、この結果ならFlatBuffersを採用するメリットはほぼない。
前回のEasySaveとの比較と同じ端末使ったけども
EasySaveは明らかに遅かった。(FlatBuffersの2倍遅いので、ZeroFormatterの2倍遅いともいえる)
ZeroFormatterはC#コードの自動生成のためコンパイル処理が必要。
EasySaveはアセットをインポートすればすぐ使える。
速度を意識しないのであれば、そのあたりはお好みで。
ZeroFormatterでデータがかけてたとき(AとBのBしか書き込まれてなかったとき)の挙動とか細かい仕様が分からないので
そのあたり、誰か調査してー。