2012年6月21日木曜日

MessagePack for CLI

ずいぶんと長い間書いていなかった気がします……
さて、github で週末ちまちま書いていた MessagePack for CLI が、公式のリポジトリに取り込まれました。びっくりです。

MessagePack とは

相互運用可能で、コンパクトバイナリ形式のオブジェクトシリアル化フォーマットまたはそのライブラリです。詳しくは 公式サイト なり、 @frsyuki さんのブログなどを参照してください。
今回はそれの .NET 用の実装を作った(ている)ということになります。

.NET 用の実装について

.NET 用の実装は実は 3 代目だったりします。初代は @neuecc さんの blog のエントリで使用されているもの。わりとざっくりとした実装で、まだまだパフォーマンスチューニングの余地がある状態でした。
2 代目は NuGet で MessagePack for C# で出てくるもの。こちらは去年の春に出てきたもので、IL 何かを使って高速化されてます。ちなみに、オブジェクトは Map(Dictionary)としてシリアル化されます。パフォーマンスはかなり高く、場合によっては protobuf-net 以上になるようです。
3 代目が for CLI で、こちらが私が作ったものになります。パフォーマンスなどについては後述します。説明についてはつたない英語ですが wiki があります

MessagePack for CLI という名前の理由

よく誤解されてるんですが、.NET 系の言語は、CLS(共通言語仕様)に準拠しているライブラリを作れば、CLI (共通言語基盤)上で動作する他の言語でも実行できます(.NET の FCL(Framework Class Library)も Mono の FCL も大部分は C# で記述されていますが、VB や F# から問題なく呼び出せているはずです)。なので、「for C#」もないかなぁと思って作り始めました。そのため、MessagePack for CLI は VB、F#、C++/CLI、PowerShell、IronRuby、IronPython などからも呼び出すことができます(FAQ に補足あります)。
そして、MessagePack がうれしいのって、どちらかというと Web の世界の方なのかと思い、Silverlight にも対応した方が良いよねとか(始めたときは 2010 年の夏ですしね)、Mono を除外する理由はないよねといったところで、for CLI にしました。たとえば、MessagePack-RPC のサーバーとして Mono を載せた Linux がいてもいいはずです。今は Unity もありますしね。

MessagePack-RPC

MessagePack-RPC というのもありまして、こちらは MessagePack を使用してシリアル化したメッセージを呼び出すフレームワークです。MessagePack-RPC for CLI もあります。Silverlight でも簡単な動作確認を行っています。これも詳細については 公式サイトwikiをご覧ください。

入手方法

プレリリース版として NuGet に上がっていますので、ご利用ください。MessagePack for CLI というアイコンに気合の感じられない方です。

パフォーマンス

前述の@neuecc さんの blog のエントリについて、自宅のマシン(Windows 7、.NET 4、x86 リリースビルド、Core i7 3770 Quad Core 3.4GHz)で測ったところ、以下のような感じです。なお、XmlSerializer についてはそもそもシリアル化できていないので割愛しました。また、逆シリアル化後のチェックロジックで DateTime 型の比較については UTC に変換し、マイクロ秒以下を無視して比較しています(端から UTC にすると JSON.NET がうまくいかなかったりとか、マイクロ秒以下が MessagePack では合わないとかあるので)。
Serialize BinaryFormatter
00:00:07.3377291
53MB
Serialize DataContractSerializer
00:00:02.2492265
149MB
Serialize DataContractSerializer Binary
00:00:01.5054592
78MB
Serialize DataContractJsonSerializer
00:00:02.7829966
47MB
Serialize DataContractJsonSerializer Binary
00:00:02.7813589
75MB
Serialize NetDataContractSerializer
00:00:12.2300278
183MB
Serialize NetDataContractSerializer Binary
00:00:10.5490878
99MB
Serialize Formatter - Protocol Buffers
00:00:00.5668601
13MB
Serialize AutoMessagePackSerializer`1 - MessagePack
00:00:00.6150166
11MB
Serialize JsonSerializer - JSON.NET
00:00:08.5988969
38MB
Serialize JsonSerializer - JSON.NET BSON
00:00:12.5111917
44MB

Deserialize BinaryFormatter
00:01:07.9430564
Deserialize XmlSerializer
00:00:02.8090175
Deserialize DataContractSerializer
00:00:06.9147897
Deserialize DataContractSerializer Binary
00:00:03.7894125
Deserialize DataContractJsonSerializer
00:00:12.3970537
Deserialize DataContractJsonSerializer Binary
00:00:06.2072323
Deserialize NetDataContractSerializer
00:00:09.4501272
Deserialize NetDataContractSerializer Binary
00:00:07.3537588
Deserialize Formatter - Protocol Buffers
00:00:01.0971126
Deserialize AutoMessagePackSerializer`1 - MessagePack
00:00:05.0464446
Deserialize JsonSerializer - JSON.NET
00:00:12.3115143
Deserialize JsonSerializer - JSON.NET BSON
00:00:12.2420603
protobuf-net にシリアル化のパフォーマンスは肉薄できているかなと思います。サイズは MessagePack の方が有利です。
ところが、逆シリアル化は protobuf-net よりもずいぶん時間がかかっています。ここは改善の余地があります(ちなみに、2 代目のパフォーマンスをはかると、そもそもシリアル化と逆シリアル化でパフォーマンスの差がここまで開いていないので、フォーマットの差というよりは MessagePack for CLI の逆シリアル化の実装に問題があると考えています。というか、Map モードで for CLI 使った場合、逆シリアル化では 2 代目の方が数倍速いです)。

はまりどころ

使ってみるとハマるかもしれない仕様についていくつか言っておきます(使ってみてがっかりされてしまうのは嫌ですし)。相互運用性のため(他にも C++、Java、D、Erlang、Ruby、Python、Haskell、Go などなど色々あります)、次の仕様上の制限があることをご承知おきくださいませ。
  • 文字列は既定では utf-8(BOM なし)になります。Encoding を指定すれば上書き可能です(相互運用性はなくなります)。
  • DateTime/DateTimeOffset は UTC の Unix Epoc になります。マイクロ秒以下の値とタイムゾーン情報は失われます。 これを回避するには、独自の MessagePackSerializer を定義するとか、ToString("o") するとかする必要があります。
  • オブジェクトは配列としてシリアル化されます。Map ではありません。そのため、長期間保存する場合、うっかりソースコードで型のフィールドの順番(MessagePackMemberAttribute カスタム属性で順序を明示的に指定できます)を変えたりすると逆シリアル化できなくなります。これは SerializationContext.SerializationMethod オプションで変更できます。

FAQ

NuGet は?
公開済みです。ただし、現在のところプレリリース版です。
今後の予定は?
とりあえず品質の安定、逆シリアル化のパフォーマンスの向上、Silverlight/Phone での自動テストの追加、WinRT サポート、IDL サポートくらいでしょうか。
IDL は言語(C# や VB)ごとに Haskell 版のパーサーに Pull Request するのがいいのか、自前で作るのが良いのかちょっと悩んでいます。GHC なんかは簡単にインストールできますが、.NET 以外入れたくない、という方もいるでしょうし。
それ以外はノーアイディアなので、気付いた点などあればご連絡いただけると嬉しいです。(Unpacker の効率改善案とか)
WinRT 対応予定は?
ちまちまやってますが、テストをどうしたものか、といったところです。
PowerShell/IronRuby/IronPython から使いづらいんですが
『.NET のクラスライブラリ設計』を訳しておきながら恐縮ですが、動的言語についてはあまり考えずに API を設計していますので、使いづらい面もあるかと思います(型引数とかそもそもの API のスタイルとか)。IronRuby、IronPython については公式リポジトリの Ruby 版や Python 版を試してみてください。もちろん、何か改善案があれば、ぜひ教えていただけると嬉しいです。
F# の API が気に食わない。お前これ名古屋でも F# 対応って言えるの?
なごやこわい。MessagePack for F# を作るなら今です。早くしないと出張中(ただし世間は GW)のホテルで帰ってきたら先を越されてへこむとかいう事態になりかねません。もちろん、改善で対応できるかもしれませんし、改善案があればいただけると嬉しいです。
戻り値が IEnumerable<T> じゃないのはおかしい
MessagePack for LINQ や MessagePack-Rx とか作ればいいじゃない。
Mono for Android や Mono Touch では動きますか?
レポートお待ちしております m(_ _)m
2012/6/21 Code Contract 周りでそもそもビルドできない問題があるようですが、次の MfA ではビルドできそうです。もちろん、ご意見等歓迎です。