ジョークで書いたrubyのMessagePackもどきgemをcで書いたら匹敵するぐらい早くなった

前回のあらすじ http://ksss9.hatenablog.com/entry/2013/10/19/131215

以前書いたAsciiPackというgemをrubyのコードからc拡張へと書き換えてみました。

AsciiPack https://github.com/ksss/AsciiPack

差分 https://github.com/ksss/AsciiPack/commit/d8a53dddd4141c46af8a936e0c6787bfc8b1d37f

これが結果として驚くほど早くなりました。(2倍〜40倍くらい)

いくつかの特徴的なオブジェクトで各メソッドを10000回呼び出して計測しました。(単位は/ms)

gist7178157

以前のはこちら https://gist.github.com/ksss/7096868

速度アップのポイントは「如何にrubyを使わないか」にあると思います。

例えばrubyのcase文では100個whenがあれば毎回100回===メソッドを呼び出すことになります。 ここの部分をc化するだけでも100回のrubyのメソッド呼び出し分のオーバーヘッドがなくなるのでかなりの効果が期待できます。 また単純な四則演算や1文字取り出すだけでもrubyならメソッド呼び出しのコストが掛かりますが、cで書けばそのコストは削減できます。

逆にc拡張化してもrb_funcallのようなrubyのメソッド呼び出しをしていては全く速度は変わりません。

文字はポインタを進めながら読み、メモリもcレベルで管理し、ruby<->cのインターフェースになる部分でrubyの内部フレームワークを使うのが良いと思いますしrubyの内部コードもそうなっています。

今回ベンチマークを取ってみて、小さな数字などではMessagePackを上回っている部分もあれば、長い文字列ではMessagePackが圧倒的な速さを出しているということがわかりました。

これはMessagePackが謳っているようにzero copyの効果だと考えられます。

AsciiPackではどんなに長い文字列でも現状では別のバッファ領域に文字をコピーしてオブジェクトを作成していて無駄だなーと思っているのでここもMessagePackを見習ってzero copyを実装できたらなと思います。

MessagePackより早くなってしまった部分については

  • メモリ管理が不十分(なんせrb_gc_markを1度も書いていない……)
  • AsciiPackの機能が単純すぎるため
  • 誤差の範囲

なんかが考えられるのかなと思います。

とりあえずオライリーの「Head first C」を買ったので早く届くといいな……。