JSONを80%削る自作バイナリ形式、jBinの作者がスキーマ言語まで作った話
Packing Binary Is Fun
Twitterで「JSONはReal™開発者向けじゃない」という議論を見た作者が、自分もReal™開発者になるべくバイナリ形式の自作に着手。単なるwbでは済まず、varintや継続ビット、型とフィールド番号を1バイトに詰め込む工夫を経て、JSONペイロードを80%縮小するスキーマ言語jBinを完成させた。
「自分だけのバイナリ形式を作るのって、どれくらい大変なんだろう?」きっとちょっとした`wb`だけだろう。( ˶ˆᗜˆ˵ ) 残念ながら、それはちょっとした`wb`だけでは済まなかった。
HNでの議論
13- trashb
私が思うに、どんな(バイナリ)フォーマットにも必要だと言える重要な要素がいくつか欠けている。
- マジックヘッダー
- バージョン番号
- CRCまたはデータ破損チェック
さらに、このケースではバイナリフォーマットをパースするよりも、カスタムのテキストパーサーを書いた方が良いと主張したい。これはUnixライクな設定ファイル vs Windowsのレジストリエディタの議論に似ている。私はJSON以外で、それでもテキストとして読めるものが好みだ。
下に示されているJSONの例でさえ、フィールド名を1文字に置き換えてスペースを削除すれば、418文字から166文字に縮小できる。このフォーマットの節約のほとんどは、フィールド名を含めず、位置依存のレイアウトにすることから来ている。区切り文字を使うか、型(+サイズ)を示すバイトを配置するかを選べる。例えば、以下の文字列は例のデータをほぼ同じ効率(85バイト vs 80バイト)でエンコードしつつ、依然として読め、UTF-8解釈もサポートする。
123456789;LeroyJenkins;60;alliance;p,100,200,300;i,999,1,1;i,45,100,0;a,s,120;a,a,45;
さらに最適化するなら、繰り返しエントリを許可し、定義済みのデフォルトから値を仮定し、差分だけを送るようにできる。これは期待するデータの型に依存する。
{ "itemId": 999, "quantity": 1, "isSoulbound": false }
i,999,1,0
は次のようになる:
default = { "itemId": 0, "quantity": 1, "isSoulbound": false }
{ "itemId": 999}
i,999
- IvanK_net
JSONの「バイナリ版」はすでにProtocol BuffersやKiwiとして存在する。これらはとてもシンプルだ(パーサーは2kBのコードで作れる)。著者が再発明した車輪を既存の車輪と比較してくれたら最高だったのに :D
- Skwid
前の仕事でのお気に入りのヤク剃り(無駄な作業)の冒険の一つは、空きメモリがほぼ同じくらいしかないデバイス上で、約1MBのデータをその場でパースするUBJSONデコーダーを書くことだった。キーによるアクセスは(十分に)速く、ほとんどが数値のペイロードに対しては、いくつかのショートカットを使った二分探索で対応した。
進行を速めるために約30バイトのインデックスを構築したが、それは新しいメッセージが先頭を上書きしている間も、古いメッセージの末尾で作業を続けられるようにするためでもあった。
システム設計が4MBのメモリのデバイスにこれらすべてを要求すべきだったか?おそらくそうではない。しかし、それはうまくいったし、とても楽しかった。