$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Attribute::Memoizeでメソッドキャッシュ

 perlでメソッドキャッシュするいい方法ないかなと探していたら、Attribute::Memoizeがよさそうだったので、使ってみました。

やりたい事

 同じメソッドにアクセスしたときに高速化するため、キャッシュを行うことってあると思います。例えばこんな感じ。

package Sample;
use strict;
use warnings;

use base qw(Class::Accessor::Fast);

sub rand_method {
    my $self = shift;
    return $self->{_rand_method} if defined $self->{_rand_method};
    return $self->{_rand_method} = rand;
}

1;

 でもこうこういうのってよく使うし、いちいち書くの面倒!って思ってました。

Attribute::Memoize

 そこで、Attribute::Memoizeを使います。Attribute::MemoizeはMemoizeの機能をAttributeを付けるだけで、使えるようにしたモジュールです。これによって簡単に上と同じようなメソッドキャッシュを実装できます。下のようにAttributeでMemoizeを付けるだけで、メソッドキャッシュされるようになります。

package Sample;
use strict;
use warnings;

use base qw(Class::Accessor::Fast);

use Attribute::Memoize;

sub rand_method : Memoize {
    my ($self) = @_;

    return rand;
}

# こっちでもちゃんとキャッシュされ、引数が同じ場合キャッシュを返すようになる
sub rand_method_having_args : Memoize {
    my ($self, @list) = @_;

    return join('/', @list) . rand;
}

1;

便利ですね!

なんでメソッドキャッシュされるの?

 引数もいい感じに処理してキャッシュしてくれるのはいいけど、なんでオブジェクトごとにちゃんとキャッシュしているのか気になったので、調べてみました。

Memoizeは管理用のハッシュに結果をキャッシュする

 http://search.cpan.org/~flora/Memoize-1.02/Memoize.pm#SCALAR_CACHE,_LIST_CACHEを参照すると、Memoizeは標準でキャッシュ方法をMEMORYにしていて、

MEMORY means that return values from the function will be cached in an ordinary Perl hash variable. The hash variable will not persist after the program exits. This is the default.

と書いてあります。つまり一度アクセスするとハッシュに結果を格納しておくということです。

Memoizeは引数一つ一つを文字列化して、つないだ物をキーとしてキャッシュする

 http://search.cpan.org/~flora/Memoize-1.02/Memoize.pm#NORMALIZERを参照すると、

The default normalizer just concatenates the arguments with character 28 in between. (In ASCII, this is called FS or control-\.) This always works correctly for functions with only one string argument, and also when the arguments never contain character 28.

と書いてあります。つまり引数一つ一つを文字列化して、character 28でjoinしたものをキー(normalize)とし、キャッシュするということですね。

つまりオブジェクトごとにキャッシュ出来る

 以上のことを合わせると、オブジェクトの場合、$selfも引数として渡ってくるので、これもキーに合わせてキャッシュしようとします。同じオブジェクトの場合、文字列化したら、同じ文字列になります。違うオブジェクトの場合は違う文字列になるので、ちゃんとオブジェクトごとにキャッシュされることになります。

最後に

 今回はオブジェクトごとにメソッドキャッシュする方法を調べて、メモしてみました。正直英語をちゃんと読めている自信がないので、間違っているかもしれません(コードはさくっとは読めなさそうな感じなので、大体しか呼んでないです...)。間違っていたら指摘お願いします。