File::Spec::Memoized - Makes File::Spec faster
File::Specはファイルパスを扱うために欠かせないモジュールだが,メソッドによっては非常に遅くプログラムのボトルネックになりかねない。
このようなケースでは,メモイズ(Memoize)というテクニックが役に立つ。メモイズは,引数が単純で副作用を持たない関数の結果をキャッシュして高速化する手法である*1。
たとえば,重い処理を行うf()があったとして,そのf()の結果が特定の引数に対して一意に決まり,かつf()に副作用がないとき,以下のように結果をキャッシュしたmemozied_f()を用意したとしても,f()とmemozied_f()の意味は変わらないが,memoized_f()の二度目以降の呼び出しがf()の呼び出しよりも高速になる。
my %cache; sub memoized_f{ # 引数からIDを作成する my $serialized_id = join "\0", @_; $cache{$serialized_id} //= f(@_); }
ただし,関数によっては$serialized_idの作成が難しいことがある。たとえば,ハッシュリファレンスのような複雑なデータ構造を受け取る場合,それに対する$serialized_idを作るのはそれなりに重い処理になるため,メモイズによって高速化するとは限らない。
しかし,File::Specのケースでは,引数が単純なスカラー値でかつundefが特別な意味を持たないため,$serialized_idを作るのは簡単である。
そこで,試しにFile::Specをメモイズしてみたところ,かなり高速になったのでFile::Spec::Memoizedとしてリリースした。
このモジュールは,ロードするだけでFile::Specのメソッドがメモイズ版に置き換わる。
ベンチマークもしてみた。もともとそれほど遅くないメソッドの場合は,劇的に速くなるわけではない。
benchmarks/splitpath.pl
$ perl -Ilib benchmarks/splitpath.pl Perl/5.10.1 (i686-linux) For splitpath(/home/s0710509/sperl/lib/5.10.1/i686-linux/File/Spec.pm) Benchmark: running Memoized, Original for at least 1 CPU seconds... Rate Original Memoized Original 163/s -- -40% Memoized 270/s 66% --
一方,劇的に高速になるメソッドもある。
benchmarks/catfile.pl
$ sperl -Ilib benchmarks/catfile.pl Perl/5.10.1 (i686-linux) For catfile( /home/s0710509/sperl/lib/5.10.1/i686-linux/File/ Spec.pm) Benchmark: running Memoized, Original for at least 1 CPU seconds... Rate Original Memoized Original 73.1/s -- -78% Memoized 336/s 360% --
File::Specを内部で使用しているPath::Classにも効果がある。
benchmarks/path_class.pl
$ sperl -Ilib benchmarks/path_class.pl Perl/5.10.1 (i686-linux) Path::Class/0.17 For new() Benchmark: running Memoized, Original for at least 1 CPU seconds... Rate Original Memoized Original 181/s -- -23% Memoized 235/s 30% -- For stringify() Benchmark: running Memoized, Original for at least 1 CPU seconds... Rate Original Memoized Original 362/s -- -55% Memoized 800/s 121% --
See also Memoize .
*1:単に「キャッシュ」と呼ぶことも多い