前回(string と byte[] を変換)、前々回(int (float, double) と byte[] を変換)に引き続きバイト変換ネタです。
byte[] に慣れてくるといきつくのは自作クラスなんじゃないかと思います。
単純なバイナリ化と、効率のいいバイナリ化について語っていこうと思います。
コードは unity なので、環境が異なる方は適宜書き換えてください(Debug.Log とか)。
単純なバイナリ化
サンプルコード
using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using UnityEngine; public class Test { [Serializable] public class MyClass { public int MemberA; public string MemberB; public List<string> MemberC; } public static void Sample() { MyClass myc = new MyClass() { MemberA = 100, MemberB = "ABC", MemberC = new List<string>() { "abc", "def", "ghi" }, }; // MyClass → バイナリ byte[] buff; using (MemoryStream ms = new MemoryStream()) { BinaryFormatter writer = new BinaryFormatter(); writer.Serialize(ms, myc); buff = ms.ToArray(); } Debug.Log($"buffer size: {buff.Length}"); // バイナリ → MyClass MyClass ret; using (MemoryStream ms = new MemoryStream(buff)) { BinaryFormatter reader = new BinaryFormatter(); ret = (MyClass)reader.Deserialize(ms); } Debug.Log($"MyClass: MemberA = {ret.MemberA}, MemberB = {ret.MemberB}, MemberC[0] = {ret.MemberC[0]}"); } }
解説
クラスをバイナリ化するのは「シリアライズ」、バイナリをクラスに戻すのは「デシリアライズ」といいます。
[Serializable]
バイナリ化するクラスにはこの属性が必要です。記述のない場合、シリアライズでエラーになります。
結果
バイナリデータは 508 バイトになった事がわかります。
また、バイナリを正しくクラスに戻せている事がわかります。
バイナリ、大きくない?
そう思った方はかなりするどいです。
たかがメンバー 5 つに保存した値の総量 15 文字程度で 508 バイトはちょっと許せないですよね。
この程度のクラスなら許容でも、クラスが肥大化した時のサイズがとても気になります。
効率のいいバイナリ化
サンプルコード
using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using UnityEngine; public class Test { [Serializable] public class MyClass { public int MemberA; public string MemberB; public List<string> MemberC; } public static void Sample() { MyClass myc = new MyClass() { MemberA = 100, MemberB = "ABC", MemberC = new List<string>() { "abc", "def", "ghi" }, }; // MyClass → Json → バイナリ string json = JsonUtility.ToJson(myc); //string json = JsonConvert.SerializeObject(myc); // NewtonSoft.Json byte[] buff; using (MemoryStream ms = new MemoryStream()) { BinaryFormatter writer = new BinaryFormatter(); writer.Serialize(ms, json); buff = ms.ToArray(); } Debug.Log($"buffer size: {buff.Length}"); // バイナリ → Json → MyClass string ret_json; MyClass ret; using (MemoryStream ms = new MemoryStream(buff)) { BinaryFormatter reader = new BinaryFormatter(); ret_json = (string)reader.Deserialize(ms); } ret = JsonUtility.FromJson<MyClass>(ret_json); // ret = JsonConvert.DeserializeObject<MyClass>(ret_json); // NewtonSoft.Json Debug.Log($"MyClass: MemberA = {ret.MemberA}, MemberB = {ret.MemberB}, MemberC[0] = {ret.MemberC[0]}"); } }
解説
MyClass → json → バイナリ化 …と、間に json を入れました。
unity でない場合、JsonUtility を Newtonsoft.Json に変更してください。
(unity でも、クラスに Dictionary や DateTime を使いたければ Newtonsoft.Json がオススメです)
結果
サイズは 1/5 以下! ちょっとひと手間加えるだけで随分違いがありますね。
なおこれ、実行速度的にも有利です。
サンプルコードを単純に 10 万回程度回してみました。Json パーサーは 2 パターン計測しています。
①JsonUtility を使う | 4.023 sec |
②Newtonsoft.Json を使う | 6.298 sec |
③クラスを単純にバイナリにする | 12.176 sec |
①JsonUtility は最速ですが型の制限が色々ある、②Newtonsoft.Json は速度では一歩 JsonUtility に及ばないものの十分早い、③単純なバイナリコンバートはちょー遅い、という結果です。
速度と汎用性を考えると Newtonsoft.Json が普段使いにはよさそうですね。
ゲームで頻繁に通信する場合
オフラインゲームではさほど気にしませんが、サーバーとの通信パケットはなるべく小さくまとめたほうがゲームの反応速度が上がり、UX の改善に繋がります。
データはこのように、ちょっとした事で改善できることがあります。「動くからいいや」をいま一度見直し、出来るだけ「楽に」、効率よく改善していきましょう!
「楽に」を忘れて改善すると、以後のメンテナンスは地獄の所業となるので、程ほどに…。