ラベル MessagePack の投稿を表示しています。 すべての投稿を表示
ラベル MessagePack の投稿を表示しています。 すべての投稿を表示

2009-12-07

MessagePack for PHP のベンチマーク測定

php_msgpackのベンチマーク測定を行いました。
比較対象は、PHP serializeとJSONです。

ベンチマークには以下のようなコードを用いました。

class MyBench
{
  private $packer;
  private $unpacker;
  private $name;
  private $time = 0;
  private $size = 0;
  private $debug = 0;

  function __construct($name, $packer, $unpacker)
  {
    $this->name = $name;
    $this->packer = $packer;
    $this->unpacker = $unpacker;
  }

  public function init()
  {
    $this->size = $this->time = 0;
  }

  public function exec($var)
  {
    $packed = $this->exec_pack($var);
    $unpacked = $this->exec_unpack($packed);
    if ($this->debug) {
      var_dump(bin2hex($packed));
      var_dump($unpacked);
    }
  }

  public function exec_pack($var)
  {
    $func = $this->packer;
    $t = microtime(true);
    $packed = $func($var);
    $this->time += microtime(true) - $t;
    $this->size += strlen($packed);
    return $packed;
  }

  public function exec_unpack($var)
  {
    $func = $this->unpacker;
    $t = microtime(true);
    $unpacked = $func($var);
    $this->time += microtime(true) - $t;
    return $unpacked;
  }

  public function dump_result($msg = "")
  {
    printf("% 25s ", $msg);
    print("$this->time sec, $this->size byte\n");
  }

  public function bench_int($num = 10000)
  {
    $this->init();
    for ($i = 0; $i < $num; $i++) {
      $this->exec($i);
    }
    $this->dump_result("$this->name (int):");
  }

  public function bench_float($num = 10000)
  {
    $this->init();
    for ($i = 0; $i < $num; $i++) {
      $this->exec((float)($i / 23));
    }
    $this->dump_result("$this->name (float):");
  }

  public function bench_string($num = 10000)
  {
    $this->init();
    $str = "";
    for ($i = 0; $i < $num; $i++) {
      $this->exec($str);
      $str .= "$i";
    }
    $this->dump_result("$this->name (string):");
  }

  public function bench_array($num = 1000)
  {
    $this->init();
    $arr = array();
    for ($i = 0; $i < $num; $i++) {
      $this->exec($arr);
      $arr []= $i;
    }
    $this->dump_result("$this->name (array):");
  }

  public function bench_map($num = 1000)
  {
    $this->init();
    $arr = array();
    for ($i = 0; $i < $num; $i++) {
      $this->exec($arr);
      $arr["k$i"] = "v$i";
    }
    $this->dump_result("$this->name (map):");
  }

  public function bench_all()
  {
    $this->bench_int();
    $this->bench_float();
    $this->bench_string();
    $this->bench_array();
    $this->bench_map();
  }
}

$packers = array(array("MessagePack", "msgpack_pack", "msgpack_unpack"),
                 array("PHP serialize", "serialize", "unserialize"),
                 array("JSON", "json_encode", "json_decode")
                 );


foreach ($packers as $p) {
  $obj = new MyBench($p[0], $p[1], $p[2]);
  $obj->bench_all();
}

実行結果は若干見づらかったので、結果を表にまとめると以下のようになりました。


 (注1.数字の単位は秒です。シリアライズ後のサイズは省略しました。
注2.便宜上、キーが文字列の配列のことをmapと書いてます。)

全体的に見るとMessagePackが優秀ですが、型によってはJSONやPHP Serializeの方が速いものもありました。
特に、文字列型ではJSONが異常に遅い一方で、PHP Serializeは健闘しています。


ちなみにMessagePackのシリアライズだけの時間を見てみると、 以下のようになりました。
       MessagePack (int): 0.0383882522583 sec, 29616 byte
     MessagePack (float): 0.0395138263702 sec, 90000 byte
    MessagePack (string): 0.187726020813 sec, 189415563 byte
     MessagePack (array): 0.0688591003418 sec, 2314272 byte
       MessagePack (map): 0.0919380187988 sec, 5287678 byte

int, float, stringに関しては大体半分くらいの時間ですが、どうやらarray のデシリアライズに異常に時間がかかっていることがわかりました。
時間があれば詳しく調べてみようと思いますが、たぶん、ひとつの配列要素を読むごとにzendオブジェクトをallocしてしまっているのが原因だと思われます。

また、takei-hさんのバージョンでも同じようにしてシリアライズの時間を測定してみました。
        MessagePack (int): 0.0359139442444 sec, 29616 byte
     MessagePack (float): 0.0392725467682 sec, 90000 byte
    MessagePack (string): 0.229011297226 sec, 189415563 byte
     MessagePack (array): 0.0804646015167 sec, 1158620 byte
       MessagePack (map): 0.0922095775604 sec, 4788178 byte

結果としては微妙に遅くなっていますがあまり大きな差はありませんでした。
配列をあらかじめ調べてmapかarrayか判断する、という処理は意外と軽いということがわかりました。
配列を長くすれば少し遅くなるのですが、すべてmap型でシリアライズしてしまった場合でもデータサイズが大きくなってその分オーバーヘッドが生じるので、デシリアライズも含めて考えると結果的にはあらかじめ調べておいた方が得になりそうです。

2009-12-01

MessagePack for PHP のarrayの扱い方について

php-msgpackでのarrayの扱い方については昨日はさらっと流してしまいましたが、arrayを実装するにしてもいろいろ方法があって悩ましいところなので、とりあえず保留しておきたかった、というのが正直なところです。

PHPでのarrayの扱い方として考えられるのは大きく分けて以下の3つくらいだと思います
  1. 不正なデータとして捨てる
  2. キーを調べてarrayかmapを判断する
  3. ユーザが指定できるようにする。

PHPだけでの使用を前提に考えると1が一番効率よいのですが、Cで書かれたサーバなどと通信するとなると不都合です。

2の方法はtake-hさんが採用されているようです。
http://tinyurl.com/yh6wgg4
この方法も考えては見たのですが、どうしてもパフォーマンスが犠牲になってしまうのであんまり実装する気は起きませんでした。

というわけで3の方法が使えるようにしたバージョンを作りました。
http://sui2.is-a-geek.net/trac/raw-attachment/wiki/php_msgpack/php5-msgpack-0.1.1.tar.gz

msgpackオブジェクトにオプションを指定することで、arrayを使うかどうかを選択できます。
$obj = new Msgpack;
$data = array("A", "B", "C");

$obj->setopt(MSGPACK_ENABLE_ARRAY, true);
var_dump(bin2hex($obj->packer($data)));

$obj->setopt(MSGPACK_ENABLE_ARRAY, false);
var_dump(bin2hex($obj->packer($data)));

 これを実行すると以下のようになります
string(14) "93a141a142a143"
string(20) "8300a14101a14202a143"

 unpackするときも同様です。
$data = pack("c*", 0x93, 0xa1, 0x41, 0xa1, 0x42, 0xa1, 0x43);

$obj->setopt(MSGPACK_ENABLE_ARRAY, true);
$obj->feed($data);
while ($obj->remain()) {
  var_dump($obj->unpacker(NULL));
}
$obj->setopt(MSGPACK_ENABLE_ARRAY, false);
$obj->feed($data);
while ($obj->remain()) {
  var_dump($obj->unpacker(NULL));
}
 実行結果
array(3) {
  [0]=>
  string(1) "A"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "C"
}
NULL

このようにユーザに適宜オプションを設定してもらうことで、2の場合程パフォーマンスを犠牲にせずにarrayをつかえますが、arraytとmapが入れ子になった場合には対応できません。


結局はオプションをいくつか用意してユーザが1~3を選べるようにするのが無難かなと思います。

2009-11-30

MessagePackのPHP Extensionを作りました

MessagePack は、id:viverさんが開発されたバイナリシリアライズ形式です。
http://d.hatena.ne.jp/viver/
http://msgpack.sourceforge.jp/

すでに、perl, ruby, pythonでは使えるようになっていますが、PHPだけはまだ無いようだったので作ってみました。

ソースはとりあえず以下からダウンロードできます。
http://sui2.is-a-geek.net/trac/raw-attachment/wiki/php_msgpack/php5-msgpack-0.1.0.tar.gz

python版のソースをベースにPHPに移植した感じになってます。


ビルドにはphpの開発環境が必要です。
Debian系のディストリビューションならphp5-devパッケージを入れれば必要なものは入ると思います。そのほかのUnix系OSでも、PHPのソースをダウンロードすれば必要なスクリプトは付属しています。

ソースをダウンロードして展開したら、以下のコマンドでインストールできます。
$ phpize
$ ./configure
$ make
$ sudo make install

make できたらPHPの.iniファイルに以下の行を追加します。
extension=msgpack.so
Debianなら/etc/php5/apache2/conf.d/や/etc/php5/cli/conf.d/以下にmsgpack.iniを配置してもOKです。


使い方はpython版と大体同じです。
$data = array(1, -1, true, false, null, "key" => "value");

// simple procedural usage
$serialized = msgpack_pack($data);
var_dump(msgpack_unpack($serialized));

オブジェクト指向型の呼び出し方法もサポートしています。
// OO usage
$obj = new Msgpack;
$serialized = $obj->packer($data);
var_dump($obj->unpacker($serialized));

Msgpackオブジェクトを使えば複数のオブジェクトを連続してデシリアライズできます。
// stream deserialize
$buf = pack("c*", 0x00, 0x40, 0x7f, 0xe0, 0xf0, 0xff);
$obj->feed($buf);
while ($obj->remain()) {
  var_dump($obj->unpacker(NULL));
}

他の言語のMessagePackと少し違うのは、array型を使用していない点です。
PHPは配列は実際には順番付けされたマップなので、 すべてmap型でシリアライズするように実装してます。
その分少しデータ量が増えてしまいますが、そこはあきらめました。

ベンチマークは後日調べてみる予定です。

ZenBackWidget