LoginSignup
8

More than 5 years have passed since last update.

[Unity] (ローカルデータの永続化をふまえて) ZeroFormatter vs FlatBuffers

Last updated at Posted at 2016-11-08

内容

ローカルにデータを保存するときにZeroFormatterとFlatBuffersのどちらが速いか比較検討する。
この手のテストは書き込むデータタイプによって偏りがでるので公平性は無視して
ある程度、普段使いしそうなデータを書き込む。
(I/Oの速度もあるしテストする機によってもアレだしね :innocent:)

データタイプは前回使ったデータタイプを使用する。

環境

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]でした:open_mouth:

実験内容

前回と同じgithubのリポジトリに収録している。
https://github.com/akerusoft/UnityTestZeroFormatter

GameController.cs
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しか書き込まれてなかったとき)の挙動とか細かい仕様が分からないので
そのあたり、誰か調査してー。

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
8