時計を壊せ

駆け出してからそこそこ経ったWebプログラマーの雑記

Memoize::Class::Constructor作った

GitHubに上げました。
名前のとおりクラスのコンストラクタをメモ化するモジュールです。
前回の反省*1を踏まえてもうちょっとIFやらなんやら煮詰めてみようって言う目論見です。

How to use

こんな感じで使います。

use HeavyClass;
use Memoize::Class::Constructor qw(HeavyClass);

もちろん複数指定することも出来ます。

use HeavyClass;
use FatClass;
use Memoize::Class::Constructor qw(HeavyClass FatClass);

他にもコンストラクタを明示的に指定したり、

use HeavyClass;
use FatClass;
use RikishiClass;
use Memoize::Class::Constructor (
   'HeavyClass',
   'FatClass',
   +{RikishiClass => 'dohyoiri'}, # コンストラクタがnew以外でもOK!
);

メモ化するタイミングを任意に出来るように、メモ化関数をインポートしたり、(ほとんどベンチマーク用だけど・・・)

use HeavyClass;
use FatClass;
use RikishiClass;
use Memoize::Class::Constructor qw(-import);

memoize_constructor(
   'HeavyClass',
   'FatClass',
   +{RikishiClass => 'dohyoiri'},
);

もちろん、自分自身に適用する事もできます。

package My::HeavyClass;
use Memoize::Class::Constructor qw(-target);

sub new{
  # ...
  bless # ...
}

# ...

1;

コンストラクタの名前がnewじゃなくても

package My::Rikishi;
use Memoize::Class::Constructor (-target => 'doskoi');

sub dosukoi{
  # ...
  bless # ...
}

# ...

1;

ところで、このモジュールではメモ化を自前で実装しているんですが、メモ化自体はコンストラクタの引数(クラス名含む)をシリアライズしたものをキーとしてハッシュにオブジェクトを保存して、
それが存在すればそれを返して、存在しなければ元のコンストラクタを呼び出して新しくオブジェクトを作ってそれを返して実現しています。

シリアライズにData::MessagePackを使っているんですが、Data::MessagePackは高速に動作する代わりにオブジェクト等を含んだデータをシリアライズする事は出来ません。
そこで、オブジェクト等を含んだ引数をコンストラクタに渡したい場合はStoable等を使ってシリアライズする等、別にキージェネレータを用意しなければなりません。

use HeavyClass;
use Storable;
use Memoize::Class::Constructor (
  -keygen => \&Storable::freeze,
  'HeavyClass',
);

こんな感じでキージェネレータを指定できます。

ベンチマーク

で、気になってくるのが速度。
以下はText::Xslateのコンストラクタを5種類の引数でそれぞれを10000回連続で呼び出した際の、Memoize::Class::Constructorを使った場合と、使わなかった場合とを比較したベンチマークです。
(メモ化を解除する手段をまだ用意していないので、サブルーチンをそれぞれ1回づつ呼び出してその中で10000回ループを回しています。)
スクリプトはgithubにあります。

Default(Data::MessagePackでシリアライズ

[karupas@devel bench]$ ./default.pl
Benchmark: timing 1 iterations of (raw), Memoize::Class::Constructor...
     (raw): 12 wallclock secs (11.01 usr +  0.31 sys = 11.32 CPU) @  0.09/s (n=1)
            (warning: too few iterations for a reliable count)
Memoize::Class::Constructor:  0 wallclock secs ( 0.47 usr +  0.00 sys =  0.47 CPU) @  2.13/s (n=1)
            (warning: too few iterations for a reliable count)
                            s/iter             (raw) Memoize::Class::Constructor
(raw)                         11.3                --                        -96%
Memoize::Class::Constructor  0.469             2315%                          --

約23倍のスピードアップに成功しています。

Storableでシリアライズ

[karupas@devel bench]$ ./keygen_storable.pl
Benchmark: timing 1 iterations of (raw), Memoize::Class::Constructor...
     (raw): 11 wallclock secs (10.07 usr +  0.26 sys = 10.33 CPU) @  0.10/s (n=1)
            (warning: too few iterations for a reliable count)
Memoize::Class::Constructor:  2 wallclock secs ( 2.65 usr +  0.00 sys =  2.65 CPU) @  0.38/s (n=1)
            (warning: too few iterations for a reliable count)
                            s/iter             (raw) Memoize::Class::Constructor
(raw)                         10.3                --                        -74%
Memoize::Class::Constructor   2.65              290%                          --

約3倍のスピードアップに成功しています。

まとめ

結構実用性の高い感じにまとまってきたんじゃないかと思います。

問題点は

  • オプションの名前が見てて気持ち悪い。
  • 名前長い。
  • インターフェースが分かりにくい。

って事とかですかね。
なんか良い名前無いでしょうか。

ドキュメントとテストがまだ書き終わってないのであれですが、
もし興味を持って頂けたなら、是非使っていただきたいです><
バグがあるかもしれませんが、見つけた場合は是非連絡下さい!

*1:Plack::Middleware::Gzip車輪の再発明だったし、Sledge::Template::XslateはIFが不便だった。(id:tokuhiromさんに指摘してもらって改善した。id:tokuhirom++)