2009-12-15

libeventとlibevの比較

libeventは何回か使ったことはあったのですが、libevの方はまだ試してなかったのでとりあえずサンプルコードを動かしてみます。

#include <ev.h>
#include <stdio.h>

ev_io stdin_watcher;
ev_timer timeout_watcher;

static void stdin_cb (EV_P_ ev_io *w, int revents)
{
    puts ("stdin ready");
    ev_io_stop (EV_A_ w);
    ev_unloop (EV_A_ EVUNLOOP_ALL);
}

static void timeout_cb (EV_P_ ev_timer *w, int revents)
{
    puts ("timeout");
    ev_unloop (EV_A_ EVUNLOOP_ONE);
}

int main (void)
{
    struct ev_loop *loop = ev_default_loop (0);

    ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);
    ev_io_start (loop, &stdin_watcher);

    ev_timer_init (&timeout_watcher, timeout_cb, 1, 0.);
    ev_timer_start (loop, &timeout_watcher);

    ev_loop (loop, 0);

    return 0;
}


これはlibev本家サイトにあったサンプルコードそのままです。
http://software.schmorp.de/pkg/libev.html

ちなみにこれを libeventで書くとこんな感じになります。
#include <sys/types.h>
#include <event.h>
#include <stdio.h> 

struct event stdin_watcher;
struct event timeout_watcher;
struct timeval t;

static void stdin_cb(int fd, short event, void *arg)
{
    puts ("stdin ready");
    event_del(arg);
    event_base_loopbreak(((struct event *)arg)->ev_base);
}

static void timeout_cb(int fd, short event, void *arg)
{
    puts ("timeout");
    event_base_loopbreak(((struct event *)arg)->ev_base);
}

int main (void)
{
    struct event_base *base = event_init();

    event_set(&stdin_watcher, 0, EV_READ, stdin_cb, &stdin_watcher);
    event_add(&stdin_watcher, NULL);
    event_base_set(base, &stdin_watcher);

    t.tv_sec = 1;
    evtimer_set(&timeout_watcher, timeout_cb, &timeout_watcher);
    timeout_add(&timeout_watcher, &t);
    event_base_set(base, &timeout_watcher);

    event_base_dispatch(base);

    return 0;
}



当然といえば当然ですがどちらも似た感じです。
というかこれだけでは見た目以外の違いがわかりませんね。

今度libevとlibeventの実装方法についても比較してみます

参考
http://libev.schmorp.de/bench.html
http://d.hatena.ne.jp/akkt/20080425/1209080621

2009-12-13

Linuxのメモリ管理について

Linuxのメモリ管理について勉強中なので、簡単なまとめをメモ。


仮想アドレスと実アドレス

多くのマルチタスクOSでは仮想アドレス空間を持っています。仮想アドレス空間の仕組みを利用すれば、物理メモリ上では不連続にマッピングされたページでも、プロセスからは連続した領域として扱うことができます。
ここでいうページとは、メモリを一定の大きさに区切って管理するための単位のことで、4Kバイトのことが多いようです。仮想アドレスと実アドレスへの変換はページ変換テーブルを用いて行われます。


実メモリの管理

実メモリはバディシステムと呼ばれる仕組みで管理されています。
バディシステムは空きページを2の冪剰の単位で分割したページフレームのリストで管理します。たとえば、4ページ分の連続メモリを確保したい場合は、まずオーダー2(2の2剰個の連続ページ)のリストから空きを探し、無かった場合は一つ上のオーダーから探します。そこで空きが見つかった場合は、そこを確保して半分に分割し、残った半分をオーダー2の空きリストに追加します。空きが見つからなかった場合は、さらに上のオーダーを再帰的に探索していきます。割り当てられる領域が見つからなかった場合はoom(out of memory) killerが実行されます。

空きページ状況は/proc/buddyinfoから参照できます
各オーダー毎につながっている空きリストの数が表示されます。
% cat /proc/buddyinfo
Node 0, zone      DMA    127    117     23      6      1      0      0      1      0      0      0
Node 0, zone   Normal    427    365     30      4      2      1      0      0      1      0      0


再起動するとフラグメンテーションが解消されていることが確認できます。
% cat /proc/buddyinfo
Node 0, zone      DMA      3      4      4      3      4      2      2      1      1      1      2
Node 0, zone   Normal      5      0      3      1      1      2      2      8      3      4     12

メモリをページ単位だけで扱うのは不便なので、より柔軟な割り当て方法としてスラブアロケータという仕組みがあります。
スラブアロケータはバディシステムから確保したメモリをキャッシュと呼ばれる同じ種類のオブジェクトの集合に分割します。ここでいうキャッシュとは、バディシステムから確保された連続した物理ページのあつまりで、キャッシュに対して割り当てられたオブジェクトの入れ物をスラブといいます。

スラブアロケータの使用状況は/proc/slabinfoを参照することで確認できます。
tunablesの値は変更することも可能です。詳細は以下の参考文献を参照してください。
limit フィールドは、CPU ごとにキャッシュに入れるオブジェクトの最大数を指定します。batchcount フィールドは、CPU 単位のキャッシュが空になったときにそこへ転送するグローバル・キャッシュ・オブジェクトの最大数です。shared パラメーターは、SMP (Symmetric MultiProcessing) システムに対する共有動作を指定します。
http://www.ibm.com/developerworks/jp/linux/library/l-linux-slab-allocator/


 ここまで調べて疲れてきたので、以下の内容はあとで加筆・修正するかもしれません。

プロセス空間の管理

プロセスに仮想アドレスを割り当てる際には様々なテクニックが用いられています。詳細は省きますが、以下のようなテクニックがあります。

スワップ
デマンドページング
コピーオンライト
ファイルマップ


プロセス空間の割り当ては、以下のようなシステムコールを用いて行うことができます。

fork
exec
mmap
brk

ページの回収

回収の対象はプロセス空間とページキャッシュで使用している実ページのうち、最近もっとも使用されていないページです。カーネルで使用しているページは回収の対象にはなりません。

処理の流れとしては以下のようになります。

* 最近参照されたかどうかチェック。
ページキャッシュの場合は、カーネルはPG_refferencedフラグをみることで知ることができます。プロセス空間にマップされたページは、ページテーブルエントリにハードウェアによってアクセスされたことが記録されるため、そこを参照することで知ることができます。
* inactiveキューに移動
* スワップ領域の割り当て
* アンマップ処理
* ダーティページの書き戻し
* ページの解放


Linuxカーネル2.6解読室   [本]
Linuxカーネル2.6解読室   [本]

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を選べるようにするのが無難かなと思います。

ZenBackWidget