MessagePack って何?

最近よく聞くMessagePackとは何かを調べたのでメモ。

MessagePackとは

バイナリでデータを保存するフォーマットです。
JSONと比べると、保存した状態の可読性を犠牲にする代わりに、
より早くて小さいフォーマットになっています。

また、汎用的なフォーマットのため、
いろんな言語(nodeとかrubyとかcppとか) で相互にデータを使えます。

他のバイナリシリアライズフォーマットとの差

MessagePackはバッファをうまく使って早かったり、 読み込み途中でもデシリアライズできるとか、
結構いろいろと高速化のための工夫がされています。
(ただし、実装によって若干違いがあるようです)

使い方

rubyならgem install msgpackで、 MacのC++ならbrew install msgpackで使えるようになります。

なお、C++はgccに以下のオプションをつける必要があります。
g++ test.cpp -lmsgpack -o test

また、基本型は全てシリアライズ可能で、保存したいクラスのメンバ変数を、
MSGPACK_DEFINEマクロに入れると、
自動でシリアライズ/デシリアライズできるみたいです。

コード

C++

#include <msgpack.hpp>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>

using namespace std;

struct User {
  User(std::string name, int id, std::vector<int> follower)
    : name(name)
    , id(id)
    , follower_id(follower)
  {}

  // 引数なしのコンストラクタは必須
  User()
    : id(0)
  {}

  MSGPACK_DEFINE(name, id, follower_id);
  std::string name;
  int id;
  std::vector<int> follower_id;

  string toString(){
    string ret = "name: ";
    ret += name;
    ret += "  id: ";
    ret += std::to_string(id);
    ret += "  follower: ";
    for(int i=0; i<follower_id.size(); ++i){
      ret += std::to_string(follower_id[i]);
      ret += " ";
    }
    return ret;
  }
};

int main(void) {
  // データを作って書き込む
  std::vector<int> follower_1;
  follower_1.push_back(1);
  follower_1.push_back(2);

  std::vector<int> follower_2;
  follower_2.push_back(2);

  std::vector<User> users;
  users.push_back(User("user1", 1, follower_1));
  users.push_back(User("user2", 2, follower_2));

  msgpack::sbuffer sbuf;
  msgpack::pack(sbuf, users);

  ofstream myFile ("cpp_data.bin", ios::out | ios::binary);
  myFile.write(sbuf.data(), sbuf.size());
  myFile.close();


  // ファイルからデータを読み込む
  char buffer[1000];
  ifstream inFile ("ruby_data.bin", ios::in | ios::binary);
  inFile.read (buffer, 1000);
  inFile.close();

  msgpack::unpacked msg;
  msgpack::unpack(&msg, buffer, 1000);

  msgpack::object obj = msg.get();
  std::vector<User> load_users;
  obj.convert(&load_users);
  for(int i=0; i<load_users.size(); ++i){
    cout << load_users[i].toString() << endl;
  }

  return 0;
}

ruby

require 'msgpack'

# load data and deserialize
users = MessagePack.unpack( File.open("cpp_data.bin") )
p users



# serialize and output data
users[0][0] = "100user"
users[0][1] = 100
users[0][2] = [100, 101]

users[0][0] = "101user"
users[0][1] = 101
users[0][2] = [101]

File.open('ruby_data.bin','w') do |f|
  f.print users.to_msgpack
end