[perl] Perlで同じコードを違うバージョンのモジュールでベンチマークする

複数バージョンのモジュールで同じコードを実行してBenchmark.pmで計測したい、というケースにこう書けばいいかな、という例。

普通にBenchmark.pmを使ってしまうと同一モジュールを違うバージョンで複数読み込むことができないため、

  • バージョンごとに子プロセスを fork
  • 子プロセスで use lib して @INC を追加してから use
  • 計測した結果をファイルに保存して親プロセスで集約、表示

という流れで書きます。

use strict;
use Benchmark qw/ :all /;
use File::Temp qw/ tempfile /;
use Storable qw/ nstore retrieve /;

# 計測したいコード
my $code = sub {
    my $c = Cache::Redis->new;
    for ( 1 .. 10 ) {
        $c->set( $_ => $_ );
        $c->get($_);
    }
};

my $result = {};
for my $name (qw/ extlib-0.01 extlib-0.02 /) {
    my ($fh, $fn) = tempfile();
    my $pid = fork;
    if ($pid) {
        close $fh;
        wait;   # 子プロセスの終了を待つ
    }
    else {
        eval qq{use lib '$name/lib/perl5'}; # 読み込み先追加
        eval qq{use Cache::Redis};          # use する
        my $r = timethis(1000, $code);      # 計測して
        nstore $r, $fn;                     # ファイルに保存
        exit;                               # 子プロセスは終了
    }
    $result->{$name} = retrieve $fn;        # 親で読み込んで集約
};

cmpthese $result; # 結果表示

あとは cpanm で、git のタグごとに別の場所に install して計測できます。

$ cpanm -n -l extlib-0.01 git://github.com/Songmu/p5-Cache-Redis.git@0.01
$ cpanm -n -l extlib-0.02 git://github.com/Songmu/p5-Cache-Redis.git@0.02
$ perl bench.pl

timethis 1000:  3 wallclock secs ( 1.49 usr +  0.53 sys =  2.02 CPU) @ 495.05/s (n=1000)
timethis 1000:  3 wallclock secs ( 1.61 usr +  0.56 sys =  2.17 CPU) @ 460.83/s (n=1000)
             Rate extlib-0.02 extlib-0.01
extlib-0.02 461/s          --         -7%
extlib-0.01 495/s          7%          --

このような働きをモジュールにして切り出したら嬉しいですかね…?

[2013-12-12 追記]

モジュール化してほしいとリクエストされたので念のため検索してみたら、Benchmark::Forking というモジュールを発見。これを使うと、上記のコードは以下のように書けます。すっきり。

use strict;
use Benchmark::Forking qw/ :all /;
 
my $code = sub {
    my $c = Cache::Redis->new;
    for ( 1 .. 10 ) {
        $c->set( $_ => $_ );
        $c->get($_);
    }
};
 
cmpthese(1000, {
    'extlib-0.02' => sub {
        unless ($INC{"Cache/Redis.pm"}) {
            eval qq{use lib 'extlib-0.02/lib/perl5'};
            eval qq{use Cache::Redis};
        }
        $code->();
    },
    'extlib-0.01' => sub {
        unless ($INC{"Cache/Redis.pm"}) {
            eval qq{use lib 'extlib-0.01/lib/perl5'};
            eval qq{use Cache::Redis};
        }
        $code->();
    },
});