mrubyでMessagePackを作った!
MessagePackって何?
MessagePackとはなにかといいますと、JSON互換の異なる言語間でデータ交換するためのシリアライズフォーマットです。JSONのような型を扱え、 JSONよりコンパクトで、速いと言われています。(mrubyではまだ性能評価していないので、今後やってみたいと思います。) ログコレクターで有名なfluentdというミドルウェアで使用されています。
MessagePack for mruby
今回、MessagePack for ruby v 0.5.5からmrubyにGEMとして移植しました。すべて移植したわけではないので、α版というところでしょうか。 MessagePack#pack,MessagePack#unpackメソッドでだいたいの型を使用出来るようになっています。
次の例は配列をシリアライズして、MessagePack#unpackメソッドでデシリアライズで元に戻しています。
msg = MessagePack.pack([1, 2, 3]) #=> "\x93\x01\x02\x03" MessagePack.unpack(msg) #=> [1, 2, 3]
または
msg = [1, 2, 3].to_msgpack #=> "\x93\x01\x02\x03" MessagePack.unpack(msg) #=> [1, 2, 3]
このようなことがmrubyでできるようになります。
それではまずMessagePack for mruby(以下mruby-msgpack)をビルドしてみましょう
ビルド
1. コマンドプロンプトでmruby-msgpackをダウンロードします:
git clone https://github.com/suzukaze/mruby-msgpack.git
2. mrubyのルートディレクトリにあるbuild_config.rb
にconf.gem
行追加します:
MRuby::Build.new do |conf| # ...(省略)... conf.gem :git => 'https://github.com/suzukaze/mruby-msgpack.git' end
3. テストコードを実行して、プログラムが正常かを確認します:
rake test
または、rakeがなければ
./minirake test
テスト結果を確認し、エラーがなければOKです(KOやCrashの値が0):
Total: 682 OK: 682 KO: 0 Crash: 0 Time: 0.068817 seconds
4. ビルドします
rake
または
./minirake
mruby-msgpackを試す
mruby-msgpackをさっそく試してみましょう。まず、コマンドプロプトから:
mruby -e 'msg = MessagePack.pack([1, 2, 3]); puts MessagePack.unpack(msg)'
[1, 2, 3]とコマンドプロンプトに表示されたでしょうか。 MessagePackモジュールにあるpackとunpackメソッドを使用して 要素3つ持った配列をシリアライズ後に、デシリアライズして元に戻しています。 今回は[1, 2, 3]をシリアライズ後にでデシリアライズしましたので、結果が[1, 2, 3]になります。
また、シリアライズは次のように配列に対してto_msgpackメソッドを呼ぶことで同じことができます。
mruby -e 'msg = [1, 2, 3].to_msgpack; puts MessagePack.unpack(msg)'
これはArrayクラスを拡張してシリアライズするto_msgpackメソッドを使用出来るようにしています。
mruby-msgpackで扱える型
ではここで、mruby-msgpackで扱える型について紹介しましょう。
- nil
- true
- false
- Fixnum
- Float
- String
- Symbol (Stringに変換されます)
- Array
- Hash (mrubyのHashは順番が保証されません。ruby 1.9からHashが順序付けに)
内部ではさらに、データの長さによって同じ型でも、フォーマットが異なります。 そこは省略しますが、短いデータには短い型情報のビット数、大きなデータには大きい型情報のビット数が割り当てられています。
rubyのMessagePackと異なる点は、mrubyにはBignumがありません。そのためmrubyではFixnumで収まらない大きな値はFloatに自動的に変換されます。これはmrubyの仕様です。Floatも扱えるのですが、大きな値でrubyと比べて値が異なることがあったのでここは要検証する必要があると思っています。
いろいろ試す
では、いろいろ試してみましょう。
1. Fixnum (整数)
1
mruby -e 'msg = 1.to_msgpack; puts MessagePack.unpack(msg)'
-1
mruby -e 'msg = -1.to_msgpack; puts MessagePack.unpack(msg)'
256
mruby -e 'msg = 256.to_msgpack; puts MessagePack.unpack(msg)'
63535
mruby -e 'msg = 65535.to_msgpack; puts MessagePack.unpack(msg)'
2. Float (浮動小数点)
1.23
mruby -e 'msg = 1.23.to_msgpack; puts MessagePack.unpack(msg)'
-1.0
mruby -e 'msg = -1.0.to_msgpack; puts MessagePack.unpack(msg)'
3. String(文字列)
"abc"
mruby -e 'msg = "abc".to_msgpack; puts MessagePack.unpack(msg)'
4. Symbol(シンボル)
:abcというシンボルは"abc"になります
mruby -e 'msg = :abc.to_msgpack; puts MessagePack.unpack(msg)'
5. true
true
mruby -e 'msg = true.to_msgpack; puts MessagePack.unpack(msg)'
6. false
false
mruby -e 'msg = false.to_msgpack; puts MessagePack.unpack(msg)'
7. nil
nilも可能ですがnil.to_sは空文字なので、if文でnilだったら、"nil!"と表示しています
mruby -e 'msg = nil.to_msgpack; puts "nil!" if MessagePack.unpack(msg) == nil'
8. Array(配列)
[1, "a", true]
mruby -e 'msg = [1, "a", true].to_msgpack; puts MessagePack.unpack(msg)'
9. Hash(ハッシュ)
{"name" => "suzukaze", "level" => 10, "list" => [1, 2, 3]}
mruby -e 'msg = {"name" => "suzukaze", "level" => 10, "list" => [1, 2, 3]}.to_msgpack; puts MessagePack.unpack(msg)' {"list"=>[1, 2, 3], "name"=>"suzukaze", "level"=>10} #順番は保証されない
最後に
mruby-msgpackはrubyにはあって、mrubyにない実装はまだありますが、ある程度使えるのではないかと思います。 ruby版には内部バッファを使用してコピー処理を抑えて高速に動作するクラスがあります。こちらも移植していきたいと思います。 なおこの実装にはIOクラス(実際にはStringIOが使用されていた)のでmruby-ioの実装にも貢献していけたならと思います。 mruby-msgpackのソースはここあります。よかったらのぞきにきてください。
mruby-fluent-loggerの作者の方がmruby-msgpackがないので代わりにmruby-jsonを使っているとのこと。mruby-msgpack作ったので、使ってください!!おかしかったら直します。