use Benchmark ベンチマークに際しての留意点

ベンチマークする時の留意点をあげてみます。ちょと長いけど興味ある人は読んでね。

ファイルを読み込んで操作するコードについて、Perl Best Practiceでは

my $code = do { local $/; <$in>}

とすることで、ファイルを一気読みすることを薦めている。
これに対して下記は、バッドなやり方として書かれているものです。

my $code;
while(my $line = <$in>)
  $code .= $line;
}

スカラー文を連結するドット演算子の . (concat)を使っているので、スピードが落ちてしまうからだという。



さて試しに二つのコードをベンチマークしてみます。

#!/usr/bin/perl

use strict;
use Benchmark qw(cmpthese);

cmpthese( 1000 , { 'slurp' => \&readline_slurp ,  'concat' => \&readline_concat });

#ファイルを読み込む
my $var_concat;
my $var_slurp;

sub readline_concat {
  my $concat;
  open($concat , "<" , "./export.txt") or die;
  while (my $line = <$concat>){
    $var_concat .= $line;
  }
}

sub readline_slurp  {
  my $slurp;
  open($slurp , "<" , "./export.txt") or die;
  $var_slurp = do { local $/; <$slurp> };
}

これをベンチマークすると結果は以下の通りになった.
concatはドット演算子を使った場合で、slurpは$/をundefにして一気読みした場合。

#一回目
concat 53.6/s     --   -91%
slurp   602/s  1025%     --
#二回目
concat 58.6/s     --   -91%
slurp   645/s  1001%     --

驚くべき差であります。なんと10倍以上も違う。すっごいなー。
しかし、慌てないでほしい。ここにもう一つの結果があるのです。

#一回目
concat 16949/s     --    -7%
slurp  18182/s     7%     --
#二回目
concat 16949/s     --    -5%
slurp  17857/s     5%     --

あれ、ほとんど速度が同じやん。さっきのとは全然違う結果だね。

・・・実はある一点を覗いて、前者と後者のベンチマークは同一のPerlコードでベンチマークしています。読み込んだファイルのサイズが違う。ただそれだけ。すんません。

10倍以上の差を見せた前者のベンチマークでは、はてなダイアリーをエクスポートしたexport.txtというテキストファイルを使いました。ファイルサイズが700KBくらいです。
それにたいして、一桁の差しか見せなかった後者のベンチマークでは、たった20行程度の書き捨てのテキストを使いました。

考察:
今回のコードにはファイル内の一行一行を処理するドット演算子が含まれています。行数の多いテキストを扱う場合はドット演算子が繰り返し呼び出されるのでオーバーヘッドが大きくなります。700KBのファイルを読み込んだ場合と、十数行のファイルを読み込んだ場合では全く違う結果が出てしまうことが予想されます。
それに対して$/変数をundefにしてファイルを一気読みする方法は、ファイルの中身を一気にメモリに放り込むだけです。ファイルサイズが大きくなっても演算の処理回数が増えることはありません。

これらのことをいろいろ考えると、今回のコードの場合は、「ファイルサイズが大きい場合に差が開き、ファイルサイズが小さい場合に大きな差が出ない」 と言えるものだったようです。(どんなフィイルを扱うにせよ$/をundefにしておいた方がいいというベストプラクティスの裏付けにもなっています。)

で、何が大事かというと、ベンチマークで速度比較する場合はファイルそのもののサイズや内容にも気を配りながらコードの比較をしないといけない!と。(今回のようなコードの場合)盲点でした。気をつけよう。


あぁ にしても10倍差を叩きだしたBestPractice!に感心。すごいすごい。