JSON/BSON/MessagePack 処理速度・データサイズ完全比較

周りでBSONやらMessagePackやらが話題になっていて,ろくに検証もせずに何騒いでんだかと思っていたら,優秀な若い連中が遅まきながらJSONとMessagePackのリアル検証をしていたので,そんな検証は単純過ぎるというのと両極端の二つだけ検証してどうするという思いから,ちょいと私も検証作業をしてみました.

まず,使用言語は,動的なガベージコレクションなどで処理速度測定が不安定になるのを避けるために,APIバインディングC/C++
対象とするシリアライズライブラリは,代表的なプレーンJSONと,MongoDBで使われているBSON,RedisやKumoFSで使われているMessagePackの3つです.だいたい,最近の軟弱なJavaプログラマーは,JavaScriptでもないのに生JSONなんてそもそも使うんじゃねーよって感じですが,それが説明できればいいなと思ってベンチマークプログラムを作ってみました.


ベンチマークは,単純にC++で僅かなHashとデータとしてわりと長く続く配列をシリアライズ後,値を検証しつつデシリアライズする単純なものです.

測定結果からいきます.

データの配列が5個っていうミニマムな場合,それから倍々にしていって,データ配列が10個,20個まで変化させ,シリアライズ・デシリアライズの速度とシリアライズ結果のデータサイズを比較してみました.

まず,データ数が5個,10個,20個の場合の,プレーンJSON,BSON,MessagePackの処理速度測定結果です.

データ数がどの場合でも,3種類のフォーマットのうち,MessagePackが最小サイズになっているのが分かります.


次に,シリアライズとデシリアライズの時間を足したトータルな処理時間をX軸に,データサイズをY軸にプロットしたグラフを作ってみます.
縦軸も横軸も原点に近くて小さいほど,優秀と言って良いと思います.

三角がMessagePack,四角がナマJSON,丸がBSONです.
BSONは,意外にプロットが一番上に来ているのが分かると思います.これは,BSONが効果的な配列ストアをサポートしていなくて,内部でハッシュに直していることに由来します.
ただ,シリアライズ・デシリアライズ速度はJSONよりはBSONが速いことも分かります.


ま,最強は,この中ではMessagePackということで.

使用したプログラムを以下に示します.

まず,JSONです.JSON-Cというライブラリを使いました.
FreeBSDでは,こんな感じに簡単に入ります.

# cd /usr/ports/devel/json-c/
# make
# make install

メインプログラムはこんな感じです.

/**********
 *  c++ json-c.cc -o json-c -Wall -I/usr/local/include -L/usr/local/lib -ljson
 **********/

#include <string>
#include <stdio.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <json/json.h>

using namespace std;

#define MAX_LOOP 100000
#define MAX_ITEMS 30
#define ITEM_PROPERTIES 1
#define MAX_BUF 32768
int main()
{
  time_t start, set_end, get_end;
  int suc = 0;
  start = time(NULL);
  json_object *obj, *keyobj, *headerobj, *valobj, *valarray, *itemarray;
  ofstream os("json-c.obj");
  for(int i=0; i<MAX_LOOP; ++i)
  {
    obj = json_object_new_array();
    char keybuf[MAX_BUF];
    snprintf(keybuf,MAX_BUF, "%010d", i);
    keyobj = json_object_new_object();
    json_object_object_add(keyobj, "key", json_object_new_string(keybuf));

    headerobj = json_object_new_object();
    json_object_object_add(headerobj, "v", json_object_new_int(1));
    json_object_object_add(headerobj, "t", json_object_new_int(time(NULL)));
    json_object_object_add(headerobj, "n", json_object_new_int(MAX_ITEMS));

    valobj = json_object_new_object();
    valarray = json_object_new_array();
    for(int j=0; j<MAX_ITEMS; ++j)
    {
      itemarray = json_object_new_array();
      for(int k=0; k<ITEM_PROPERTIES; ++k)
      {
        json_object_array_add(itemarray, json_object_new_int(k));
      }
      json_object_array_add(itemarray, json_object_new_int(time(NULL)));
      json_object_array_add(valarray, itemarray);
    }
    json_object_object_add(valobj, "val", valarray);

    json_object_array_add(obj, keyobj);
    json_object_array_add(obj, headerobj);
    json_object_array_add(obj, valobj);

    ++suc;
    os<<json_object_to_json_string(obj)<<endl;
    array_list_free(json_object_get_array(obj));
  }
  ifstream is("json-c.obj");
  set_end = time(NULL);

  for(int i=0; i<MAX_LOOP; ++i)
  {
    char keybuf[MAX_BUF], line[MAX_BUF];
    is.getline(line, MAX_BUF);
    obj = json_tokener_parse(line);
    if(obj == NULL) 
    {
      cout<< "ERROR" <<flush<<endl;
      exit(0);
    }
    snprintf(keybuf,MAX_BUF, "%010d", i);
    json_object *key = json_object_object_get(json_object_array_get_idx(obj, 0), "key"); // Get key

    if(strncmp(keybuf, json_object_get_string(key), 10) != 0) continue;

    json_object *valobj = json_object_array_get_idx(obj, 1); // Get header
    json_object *v = json_object_object_get(valobj, "v");
    json_object *t = json_object_object_get(valobj, "t");
    json_object *n = json_object_object_get(valobj, "n");
    if(json_object_get_int(v) != 1 || 
       json_object_get_int(t) < 0 || 
       json_object_get_int(n) != MAX_ITEMS) continue;
    valarray = json_object_object_get(json_object_array_get_idx(obj, 2), "val"); // Get value
    json_object ** item = new json_object*[ITEM_PROPERTIES];
    for(int j=0; j<MAX_ITEMS; ++j)
    {
      itemarray = json_object_array_get_idx(valarray, j);
      for(int k=0; k<ITEM_PROPERTIES; ++k)
      {
        item[k] = json_object_array_get_idx(itemarray, k);
        if(json_object_get_int(item[k]) != k)
        {
          k = ITEM_PROPERTIES;
          j = MAX_ITEMS;
          delete[] item;
          item = NULL;
        }
      }
    }
    if(item == NULL) continue;
    ++suc;
    array_list_free(json_object_get_array(obj));
  }

  get_end = time(NULL);

  printf("try=%d success=%d ratio=%f\n",MAX_LOOP,suc/2,(double)suc/2/MAX_LOOP*100.0);

  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n",
         set_end-start, get_end-set_end, get_end-start);

  return(0);

}

次にBSONです.
本家,MongoDBのものを使いました.なので,余計なboostみたいなデカイライブラリを必要としています.
それでも,FreeBSDでインストールするには,こんな感じで簡単です.

# cd /usr/ports/databases/mongodb/
# make
# make install

ベンチマークプログラムはこんな感じです.

/**********
 *  c++ bson.cc -o bson -Wall -I/usr/local/include -L/usr/local/lib -lboost_filesystem -lboost_system -lboost_thread -lmongoclient
 **********/

#include <string>
#include <stdio.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <mongo/bson/bson.h>

using namespace std;
using namespace mongo;

#define MAX_LOOP 100000
#define MAX_ITEMS 30
#define ITEM_PROPERTIES 1
#define MAX_BUF 32768
int main()
{
  time_t start, set_end, get_end;
  int suc = 0;

  start = time(NULL);

  BSONObj obj, keyobj, headerobj, valobj, valarray, itemarray;
  ofstream os("bson.obj");

  for(int i=0; i<MAX_LOOP; ++i)
  {
    BSONObjBuilder objb, keyobjb, headerobjb, valobjb;
    char keybuf[MAX_BUF];
    snprintf(keybuf,MAX_BUF, "%010d", i);
    keyobjb.append("key", keybuf);

    headerobjb.append("v", 1);
    headerobjb.append("t", time(NULL));
    headerobjb.append("n", MAX_ITEMS);

    BSONArrayBuilder  objarrayb, valarrayb;
    for(int j=0; j<MAX_ITEMS; ++j)
    {
      BSONArrayBuilder  itemarrayb;
      for(int k=0; k<ITEM_PROPERTIES; ++k)
      {
        itemarrayb.append(k);
      }
      itemarrayb.append(time(NULL));
      valarrayb.append(itemarrayb.arr());
    }
    valobjb.append("val", valarrayb.arr());

    objarrayb.append(keyobjb.obj());
    objarrayb.append(headerobjb.obj());
    objarrayb.append(valobjb.obj());
    obj = objarrayb.arr();
    ++suc;
    os.write(obj.objdata(),obj.objsize());
  }
  ifstream is("bson.obj");
  set_end = time(NULL);

  for(int i=0; i<MAX_LOOP; ++i)
  {
    char keybuf[MAX_BUF], line[MAX_BUF];
    int size;
    is.read((char *)&size, sizeof(int));
    is.read(line+sizeof(int), size-sizeof(int));
    memcpy(line, (char *)&size, sizeof(int));
    obj = BSONObj(line);
    vector<BSONElement> objarrayb;
    obj.elems(objarrayb);

    snprintf(keybuf,MAX_BUF, "%010d", i);
    BSONElement key = objarrayb[0]["key"];  // Get key

    if(strncmp(keybuf, key.String().c_str(), 10) != 0) continue;

    BSONElement valobj = obj[1]; // Get header
    BSONElement v = valobj["v"];
    BSONElement t = valobj["t"];
    BSONElement n = valobj["n"];
    if(v.Int() != 1 || 
       t.Int() < 0 || 
       n.Int() != MAX_ITEMS) continue;

    vector<BSONElement> itemarray = objarrayb[2]["val"].Array();
    for(int j=0; j<MAX_ITEMS; ++j)
    {
      vector<BSONElement> item = itemarray[j].Array();
      for(int k=0; k<ITEM_PROPERTIES; ++k)
      {
        if(item[k].Int() != k)
        {
          k = ITEM_PROPERTIES;
          j = MAX_ITEMS;
          itemarray.clear(); 
        }
      }
    }
    if(itemarray.size() == 0) continue;
    ++suc;
  }

  get_end = time(NULL);

  printf("try=%d success=%d ratio=%f\n",MAX_LOOP,suc/2,(double)suc/2/MAX_LOOP*100.0);

  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n",
         set_end-start, get_end-set_end, get_end-start);

  return(0);
}

最期はMessagePack版です.
ライブラリのインストールは,FreeBSDではこんな感じです.

# cd /usr/ports/devel/msgpack/
# make
# make install

ベンチマークプログラムはこんな感じです.ストリーミング・デシリアライズルーチンを使っています.

/**********
 *  c++ msgpack.cc -o msgpack -Wall -I/usr/local/include -L/usr/local/lib -lmsgpack 
 **********/

#include <string>
#include <stdio.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <msgpack.hpp>

using namespace std;
using namespace msgpack;

#define MAX_LOOP 100000
#define MAX_ITEMS 30
#define ITEM_PROPERTIES 1
#define MAX_BUF 32768
int main()
{
  time_t start, set_end, get_end;
  int suc = 0;

  start = time(NULL);

  sbuffer obj, keyobj, headerobj, valobj, valarray, itemarray;
  ofstream os("msgpack.obj");

  typedef type::tuple<map<string, string>, map<string, int>, map<string, vector<vector<int> > > >doctype;
  doctype doc;

  for(int i=0; i<MAX_LOOP; ++i)
  {
    map<string, string> keyb;
    map<string, int>  headerobjb;
    map<string, vector<vector<int> > > valobjb;
    char keybuf[MAX_BUF];
    snprintf(keybuf,MAX_BUF, "%010d", i);
    keyb["key"] = keybuf;
    headerobjb["v"] = 1;
    headerobjb["t"] = time(NULL);
    headerobjb["n"] = MAX_ITEMS;

    vector<vector<int> > valarrayb;
    for(int j=0; j<MAX_ITEMS; ++j)
    {
      vector<int> itemarrayb;
      for(int k=0; k<ITEM_PROPERTIES; ++k)
      {
        itemarrayb.push_back(k);
      }
      itemarrayb.push_back(time(NULL));
      valarrayb.push_back(itemarrayb);
    }
    valobjb["val"] = valarrayb;

    doc = doctype(keyb, headerobjb, valobjb);

    pack(obj, doc);
    ++suc;
   os.write(obj.data(),obj.size());
    obj.clear();
  }

  ifstream is("msgpack.obj");
  set_end = time(NULL);

  char keybuf[MAX_BUF];
  unpacker line;

  int i = 0;
  while(i < MAX_LOOP)
  {
    line.reserve_buffer(MAX_BUF);
    ssize_t len = is.readsome(line.buffer(), line.buffer_capacity());
    if(len == 0) break;
    line.buffer_consumed(len);
    while(line.execute())
    {
      object msg = line.data();
      line.release_zone();
      line.reset();
      snprintf(keybuf,MAX_BUF, "%010d", i);
      doctype doc = msg.as<doctype>();
      string key = doc.get<0>()["key"];
      if(strncmp(keybuf, key.c_str(), 10) != 0) continue;
      else ++i;
      map<string, int>  valobj = doc.get<1>();
      int v = valobj["v"];
      int t = valobj["t"];
      int n = valobj["n"];
      if(v != 1 || 
         t < 0 || 
         n != MAX_ITEMS) continue;

      map<string, vector<vector<int> > >itemarrayval = doc.get<2>();
      vector<vector<int> > itemarray = itemarrayval["val"];
      for(int j=0; j<MAX_ITEMS; ++j)
      {
        vector<int> item = itemarray[j];
        for(int k=0; k<ITEM_PROPERTIES; ++k)
        {
          if(item[k] != k)
          {
            k = ITEM_PROPERTIES;
            j = MAX_ITEMS;
            itemarray.clear(); 
          }
        }
      }
      if(itemarray.size() == 0) continue;
      ++suc;
    }

  }

  get_end = time(NULL);

  printf("try=%d success=%d ratio=%f\n",MAX_LOOP,suc/2,(double)suc/2/MAX_LOOP*100.0);

  printf("set=%d[sec], get=%d[sec], total=%d[sec]\n",
         set_end-start, get_end-set_end, get_end-start);
  return(0);

}