Unity & サーバー間通信でのMessagePack導入奮闘記(Unity編)

前回、MessagePack導入の背景とサーバー側の実装についての記事を紹介しましたが、
今回はクライアント側(Unity)の実装と、そのパフォーマンスについて紹介します。

MePack for CssageLI

Unity(C#)でMessagePackを扱うためのライブラリとして、公式が提供しているMessagePack for CLIを選びました。

オブジェクトのシリアライズとデシリアライズをするためのコードは以下のようになります。

//パック
MemoryStream stream = new MemoryStream();
var serializer = MessagePackSerializer.Create<SampleRequest>();
serializer.Pack(stream, mHttpParam.requestParam);

data = new byte[(int)stream.Length];
stream.Position = 0;   
stream.Read(data, 0, (int)stream.Length);


//アンパック
var serializer = MessagePackSerializer.Create<SampleResponse>();
SampleResponse response = serializer.UnpackSingleObject(bytes);

MessagePack for CLIではデフォルトではリストArray型(リスト)でシリアライズするようになっていますが、これはSerializationContextで設定を変更することができます。

var context = new SerializationContext();
context.SerializationMethod = SerializationMethod.Map;//Map型
var serializer = MessagePackSerializer.Get<T>(context);

前回の記事でも紹介したとおり、Map型の場合はKey-Valueの情報となるため、ValueのみのArray型と比べてデータ量が多くなってしまうので、今回はArray型を使用することにしました。

Array型では、サーバーとプロパティの順序をあわせる必要があります。

以下のようにアトリビュートを指定することで、その順番を指定できます。

//サンプルAPIのリクエストパラメータ
public class SampleRequest {
    [MessagePackMemberAttribute(0)]
    public int sampleInt;
    [MessagePackMemberAttribute(1)]
    public float sampleFloat;
    [MessagePackMemberAttribute(2)]
    public bool sampleFlg;
    [MessagePackMemberAttribute(3)]
    public string sampleString;
    [MessagePackMemberAttribute(4)]
    public List<SampleSubClass> sampleList;
}

パフォーマンス検証

クライアントとネイティブでArray型のMessagePackを扱うことが出来るようになったので、JSON(gzip圧縮)とArray型のMessagePackのパフォーマンスチェックを行いました。

検証にはクライアントにはiPhone5を、実際のモバイル回線を経由してサーバーに接続し、以下の項目を計測しました。

  • クライアントでのシリアライズ時間
  • クライアント・サーバーでの通信時間
    • JSONのgzip圧縮はここに含まれます
  • クライアントでのデシリアライズ時間

計測は通信を100回行い、その平均時間としました。

検証には以下のクラスで表現される固定のデータを、リスト形式のデータを用意しました。

import java.io.Serializable;
import java.util.Date;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.msgpack.annotation.Ignore;
import org.msgpack.annotation.Index;
import org.msgpack.annotation.Message;
/** 
 * Object4Sample
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@Message
@EqualsAndHashCode(callSuper = false)
public class Object4Sample  implements Serializable {
    /** serialVersionUID */
    @Ignore
    @Getter(value = AccessLevel.NONE)
    private static final long serialVersionUID = 1L;
    /**  */
    @Index(0)
    private Integer intValue;
    /**  */
    @Index(1)
    private Long longValue;
    /**  */
    @Index(2)
    private Short shortValue;
    /**  */
    @Index(3)
    private Boolean boolValue;
    /**  */
    @Index(4)
    private Float floatValue;
    /**  */
    @Index(5)
    private Double doubleValue;
    /**  */
    @Index(6)
    private String strValue;
    /**  */
    @Index(7)
    private Date dateValue;
}

データサイズについて

Object4Sampleをリストで保持し、その要素数を100・1000・10000とした時のJSON(非圧縮)とMessagePack形式のデータのサイズは以下のとおりです(単位はbyteです)。

圧縮率は約3.6倍となりました。

要素数 JSON MessagePack(Array)
100 16458 4524
1000 161358 45024
10000 1610358 450024

検証結果

検証結果を示します(単位はミリ秒です)。

クライアントでのシリアライズ時間

要素数 JSON MessagePack(Array)
100 25.57 2.75
1000 238.53 17.06
10000 2261.18 156.12

クライアントでのデシリアライズ時間

要素数 JSON MessagePack(Array)
100 34.34 5.33
1000 333.72 47.83
10000 3483.91 505.3

クライアント・サーバー間の通信時間

要素数 JSON MessagePack(Array)
100 340.61 418.08
1000 571.48 502.81
10000 2494.57 1493.67

検証結果のまとめ

上記の結果を加味して、プロジェクトではArray型のMessagePackの導入を決定しました。

  • JSONとくらべてMessagePackの方がデータサイズが小さい点
  • クライアントでのシリアライズ・デシリアライズ時間が、要素数に応じて大きく短縮できている点
  • クライアント・サーバー間通信の時間は、要素数に応じて短縮される点

まとめ

簡単にですがUnity(C#)でMessagePackの導入例とクライアント・サーバーでの各フォーマットのパフォーマンスチェックの結果を示しました。

また機会があれば、以下のように実際に使用する上で苦労していることや、工夫していることについて記載していきたいと思います。

・Array型で苦労する点とその対策(サーバーとクライアント側の定義自動化)
・iOS実機でのAOT問題