読者です 読者をやめる 読者になる 読者になる

Introducing the MessagePack

高速なシリアライズライブラリ MessagePack の新しいWebサイトをオープンしました!


The MessagePack Project
http://msgpack.sourceforge.net/

Ruby Inside でも取り上げられたようです: MessagePack: Efficient, Cross Language Binary Object Serialization


昨今、効率を重視したシリアライズライブラリが数多く登場しています。特に、大量の処理を行う大規模な基盤システム向けに開発されていることが多いようです。

少し探してみるだけでも、次のような事例が見つかります:


サーバの台数を増やして性能を伸ばさざるを得ないケースでは、システム全体の負荷に占める通信(メッセージング)の負荷の割合が高くなりがちです。そもそも通信の実装自体も大変です。
従って、メッセージングを制する者が基盤システムを制すると言っても過言では無い…かもしれません。ぃゃ過言かもしれませんが^^;
ともあれ、効率が良く、使い勝手にも優れたシリアライズライブラリがあると便利です。そこで MessagePack はどうでしょう?というわけです。

MessagePackとは?

MessagePackは効率の良いシリアライズライブラリです。オブジェクトをバイト列に変換したり、バイト列からオブジェクトを復元したりすることができます。凄まじいコミッタの方々の手によって各種言語に移植されており、異なる言語で書かれたプログラムの間でオブジェクトを交換することができます。
簡単に言い換えれば「速いJSON」です。JSONと比較するとシリアライズ後のデータサイズが小さく、CPUへの負荷が小さくて、ずっと高速です。


シリアライズできるオブジェクトの型は、JSONとほぼ同じです。Nil、Boolean、整数、浮動小数点数、文字列、配列、連想配列を格納することができます。現在JSONを利用しているシステムは、MessagePackに置き換えるだけで性能が向上するかもしれません.


MessagePack の特徴には、まず ストリームデシリアライザ があります。これは他のシリアライズライブラリにはあまり見られない機能です。
また、一部の実装(今のところC++Ruby)は、ゼロ・コピー化 されており、特に大きなバイト列を扱うケースでは、他のシリアライズライブラリと比べて圧倒的に高速です。


フォーマット仕様は、細かいデータ(小さい整数や短い配列・連想配列)を非常に小さいバイト数でシリアライズできるようになっています。"]"や"}"などのメタ文字を多く含むJSONと比べると、ずっと小さいサイズでオブジェクトをシリアライズできます。

MessagePack の spec をみても解るとおり、15要素以下の HASH に1バイト、 7bit 以下の unsigned int では1バイトの容量しかかからないので、上記の圧縮後の value は5バイトという驚異的なサイズとなって kumofs の value に格納されます。


kumofs での Data::Model の使い方

ゼロ・コピー シリアライズ

冒頭のベンチマークテストのグラフは、MessagePack と Protocol Buffers、JSON のシリアライズ・デシリアライズ速度を比較したものです。3つの整数と512バイトの文字列からなるオブジェクトを20万個シリアライズして、さらにデシリアライズするのにかかった時間を測定しています。言語にはC++を使っています。

結果を見ると、MessagePack は Protocol Buffers より4倍高速です。これはゼロ・コピー化されたシリアライズとデシリアライズが威力を発揮しています。


今回比較した Protocol Buffers や YAJL(Yet Another JSON Library)を含め、通常のシリアライズライブラリでは、シリアライズするときにデータをすべて1つのバッファにコピーします:


長いバイト列を扱うケースでは、この処理は非常に重い処理になります。
これに対して MessagePack の一部の実装(今のところC++のみ)では、長いバイト列はコピーせずにシリアライズすることができます:


この機能を使うと、シリアライズ結果は不連続なバッファ(struct iovecの配列)になります。不連続なバッファでも、writevシステムコールを使うと直接ファイルやソケットに書き出すことができます。オブジェクトをファイルに保存したいときや、ネットワーク越しに別のホストに送信したいときに有効です。


kumofs では、このゼロ・コピー化されたシリアライズ/デシリアライズと、MessagePack に統合されたメモリ管理機構を全面的に活用しています。これによって長い key や value でも効率よく扱えるようになっています。

ゼロ・コピー デシリアライズ

MessagePackの一部の実装(今のところC++Rubyのみ)では、バッファからオブジェクトを復元するときに、長いバイト列をコピーせずに復元することができます:


Ruby版の実装では、Copy-on-Writeを利用しています。つまり、参照先のバッファが変更しても、参照元のオブジェクトには影響しません。このため、バッファがデシリアライズされたオブジェクトから参照されているか否かを意識する必要がありません。単にゼロ・コピー化された恩恵だけを享受できます。

ストリームデシリアライザ

ネットワークやパイプなどのトリームからデータを受信する場合は、バッファリングが重要になります。
MessagePack では、バッファリングしている最中のデータに対してデシリアライズ処理を進めることができ、1つのオブジェクトをデシリアライズするのに必要なデータがすべて揃っていない状態でも、途中までデシリアライズ処理を進めていくことができます。
この機能をストリームデシリアライザと呼んでいます。


実際のプログラミングでは、データが届き次第、バッファを次々に追記していくだけで、オブジェクトを1つずつ取り出すことができます。「データを受け取る前にデータの長さを受け取る」などなど、ネットワークプログラミングでありがちな面倒な処理を書く必要が無いので、プログラムが簡単になるメリットがあります。
また、ストリームからデータを受信する処理とデシリアライズを進める処理をオーバーラップさせることで、デシリアライズにかかる処理時間を隠蔽し、遅延を短縮することができます。


例えばRubyでストリームデシリアライザを使うと、以下のようなコードになります:

require 'rubygems'
require 'eventmachine'    # EventMachine
require 'msgpack'
require 'json'

# MessagePack を受け取って JSON で返すサーバ
class MessagePackToJSON < EventMachine::Connection
  def initialize(*args)
    super
    @pac = MessagePack::Unpacker.new  # ストリームデシリアライザ
  end

  def receive_data(data)    # データを受信したら...
    @pac.feed(data)         # 1. バッファに追記
    @pac.each {|msg|        # 2. オブジェクトを取り出す
      receive_object(msg)   # 3. いろいろする
    }
  end

  def receive_object(msg)
    puts "message received: #{msg.to_json}"
    send_data msg.to_json
  end
end

EventMachine::run do
  EventMachine::start_server "0.0.0.0", 9191, MessagePackToJSON
end


C++版ではこんな感じです:

#include <unistd.h>
#include <iostream>
#include <msgpack.hpp>

int main(void) {
    // 標準入力から読み込む
    int fd = 0;
    msgpack::unpacker pac;
    ssize_t count;

    while(true) {
        // バッファを予約して...
        pac.reserve_buffer(64*1024);

        // バッファにデータを読み込む
        count = read(fd, pac.buffer(), pac.buffer_capacity());
        if(count <= 0) {
            return 0;
        }
        pac.buffer_consumed(count);

        // オブジェクトを取り出す
        while(pac.execute()) {
            msgpack::object msg = pac.data();
            std::auto_ptr<msgpack::zone> z( pac.release_zone() );
            pac.reset();

            // いろいろする。iostreamに出力
            std::cout << "message received: " << msg << std::endl;
        }
    }
}

MessagePackに関する情報

MessagePackのドキュメントは、トップページからたどれる各ページや、Wiki日本語)にあります。
リポジトリはgithubにあります:http://github.com/msgpack/msgpack
Webサイトもgithubで管理しています:http://github.com/msgpack/website
それから Twitter#msgpack には色々な情報が流れています。


MessagePackを利用したメッセージングライブラリの開発も進行中です:http://github.com/msgpack/msgpack-rpc
新しい情報があれば、随時このブログやTwitterでお知らせしていく予定です。