こんにちは!
システム開発部のK.Mです。
前置き
前々回と前回のMemoryPackの記事にて、MemoryPackを色々触ってみましたが、まだ少しやり残したことがありましたので、やり残した分の内容となっております。
速度確認
公式では数倍違うという記載になっておりましたが、実際のところどれ位違うのかをMessagePackとMemoryPackで速度計測してみました。
下記の3ケースで後述しているデータ構造体40万件をサーバから送り、クライアント(Unity)にてデシリアライズ処理をする箇所にて計測しています。
- MessagePack(未圧縮:約20MBのデータサイズ)
- MemoryPack(未圧縮:約14MBのデータサイズ)
- MessagePack(LZ4圧縮:約4MBのデータサイズ)
- 念のため参考用
MemoryPackの圧縮がLZ4がデフォルトで記事作成している現在未対応になっているので、対応されるのを期待したいところです!
- https://neue.cc/2022/11/04_memorypack.html
MemoryPackの実装と統合された効率的な圧縮については、現在BrotliEncode/Decodeのための補助クラスを標準で用意しています。しかし、性能を考えるとLZ4やZStandardを使えたほうが良いため、将来的にはそれらの実装も提供する予定です。
データ構造
public partial class WeatherForecast { public DateTime Date { get; set; } public int TemperatureC { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string? Summary { get; set; } }
Summaryのstring型は必ず半角英数字スペースで12文字が設定される形にしています。
計測結果
1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 | |
---|---|---|---|---|---|---|
MessagePack (未圧縮) | 337ms | 346ms | 337ms | 341ms | 338ms | 339.8ms |
MemoryPack (未圧縮) | 251ms | 262ms | 313ms | 260ms | 256ms | 268.4ms |
MessagePack (LZ4圧縮) | 416ms | 413ms | 409ms | 426ms | 451ms | 423ms |
データ構成がよろしくなかったのか数倍という感じまでは行きませんでしたが、MemoryPackがやはり速いという結果になりました。
MemoryPackに標準でLZ4などの圧縮対応が入れば、送受信時のデータ容量が減り、MessagePackより速いという形となり、C#だけの環境であればMemoryPack一択になりそうです。
バージョン耐性
基本的にサーバとクライアント間で同じリクエストとレスポンスを定義したファイルを使っていれば、データ構成が異なっていて正常に処理が出来ないということは発生しないのですが、MemoryPackのReadMeにバージョン耐性の説明が存在するため、興味本位で色々と確認してみました。
POST対応
バージョン耐性を確認する前に前回、前々回のものですと、GETの対応しか入れていなかったのでPOSTの対応を入れます。
サーバ側
この辺りを参考にしてPOSTで貰った値をオウム返しで同じものを返すものを作成しました。
[HttpPost(Name = "PostWeatherForecast2")] public ActionResult<TodoItemOut> Post(TodoItem todoItem) { TodoItemOut res = new TodoItemOut { Id = todoItem.Id, IsComplete = todoItem.IsComplete.ToString(), Name = todoItem.Name }; return res; }
TodoItemとTodoItemOutの中身は下記のようになっております。
using System.Runtime.InteropServices; using MemoryPack; namespace TestMemoryPack; [MemoryPackable] public partial class TodoItem { public long Id { get; set; } public string? Name { get; set; } public bool IsComplete { get; set; } }
クライアント側
POST用の処理を下記の様に用意しました。
SendWebRequest以降はGET時と同じような形となりますので省いております。
TodoItem data = new TodoItem(); data.Id = 1; data.IsComplete = true; data.Name = "test"; // memorypackでシリアライズ byte[] reqData = MemoryPackSerializer.Serialize(data); var request = new UnityWebRequest("http://IPアドレス:ポート/WeatherForecast2", "POST"); request.uploadHandler = (UploadHandler)new UploadHandlerRaw(reqData); request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/x-memorypack"); request.SetRequestHeader("Accept", "application/x-memorypack"); yield return request.SendWebRequest();
確認内容
いきなり結論からですが、ほとんどケースでシリアライズかデシリアライズ時にエラーが発生するか、エラーが出ない場合は無理やり型にはめ込んでいるので、データ壊れが発生しておりました。
サーバのリクエスト/レスポンス定義は変わらず、クライアントのリクエスト/レスポンスの定義を変更
運用時ほぼ発生しないはず。
- リクエストの順番変更
- リクエストの項目追加
- 最初に追加
- 最後に追加
- 途中に追加
- リクエストの型変更(項目数そのまま)
- レスポンスの順番変更
- 結果:クライアント側でデシリアライズでエラー発生
Length header size is larger than buffer size, length: 数字.
- 順番の変え方次第でエラーにならずにデータ壊れのケースもあった
- レスポンスの項目追加
- レスポンスの型変更(項目数そのまま)
- 結果:クライアント側でデシリアライズでエラー発生 か エラー発生せずにデータ壊れ
Sequence reached end, reader can not provide more buffer.
サーバのリクエスト/レスポンス定義を変更、クライアントのリクエスト/レスポンス定義は変わらず
こちらは運用時にミスをした場合にはありえそう。
- リクエストの順番変更
- 結果:サーバ側のシリアライズ時にエラー発生 か エラー発生せずにデータ壊れ
Length header size is larger than buffer size, length: 数字.
- リクエストの項目追加
- リクエストの型変更(項目数そのまま)
- 結果:サーバ側のシリアライズ時にエラー発生 か エラー発生せずにデータ壊れ
Sequence reached end, reader can not provide more buffer.
- レスポンスの順番変更
- 結果:クライアント側のシリアライズ時にエラー発生
Length header size is larger than buffer size, length: 数字.
Sequence reached end, reader can not provide more buffer.
- レスポンスの項目追加
- 最初に追加
- 結果:クライアント側のシリアライズ時にエラー発生
Current object's property count is 3 but binary's header maked as 4, can't deserialize about versioning.
- 最後に追加
- 結果:クライアント側のシリアライズ時にエラー発生
Current object's property count is 3 but binary's header maked as 4, can't deserialize about versioning.
- 途中に追加
- 結果:クライアント側のシリアライズ時にエラー発生
Current object's property count is 3 but binary's header maked as 4, can't deserialize about versioning.
- 最初に追加
- レスポンスの型変更(項目数そのまま)
- 結果:クライアント側のシリアライズ時にエラー発生
Sequence reached end, reader can not provide more buffer.
Length header size is larger than buffer size, length: 数字.
最後に
リベル・エンタテインメントでは、このような最新技術などの取り組みに興味のある方を募集しています。
もしご興味を持たれましたら下記サイトにアクセスしてみてください。
https://liberent.co.jp/recruit/