涼風コンピュータblog

涼風 ・Rubyist, RubyやRuby on Railsに詳しくなっていきたいです

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.rbconf.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作ったので、使ってください!!おかしかったら直します。