Perl覚え書き20041209

 それは「太陽誘電を使っている限り海賊版ではありません。あなたは今までに海賊版太陽誘電が使われているのを見たことがありますか?無いですよね。だから海賊版ではないんです」*1と同レベルの理屈のような気がする…いや、なんでもないです。

 さて、何はともあれベンチマークを取るだけなら特に悩むほどの事ではないからとりあえずやってみた。ソースはこんな感じ。

use Benchmark;
$tag_regex_ = q{[^"'<>]*(?:"[^"]*"[^"'<>]*|'[^']*'[^"'<>]*)*(?:>|(?=<)|$(?!\n))}; #'}}}}
$comment_tag_regex =
    '<!(?:--[^-]*-(?:[^-]+-)*?-(?:[^>-]*(?:-[^>-]+)*?)??)*(?:>|$(?!\n)|--.*$)';
$tag_regex1 = qq{$comment_tag_regex|<$tag_regex_};

$tag_regex_ = q{(?:(\s*)(?:([^\s=]+)\s*(=)\s*)?("[^"]*"|'[^']*'|[^\s<>]*))};
$tag_regex2 = qq{$comment_tag_regex|<$tag_regex_+>};

$tag_regex_ = q{(?:\s*(?:[^\s=]+\s*=\s*)?(?:"[^"]*"|'[^']*'|[^\s<>]*))};
$tag_regex3 = qq{$comment_tag_regex|<$tag_regex_+>};

$tag_regex_ = q{([^\s<>]+)(?:(\s+)(?:([^\s=]+)\s*(=)\s*)?("[^"]*"|'[^']*'|[^\s<>]*))*};
$tag_regex4 = qq{$comment_tag_regex|<$tag_regex_>};

$tag_regex_ = q{[^\s<>]+(?:\s*(?:[^\s=]+\s*=\s*)?(?:[^\s<>]*|"[^"]*"|'[^']*'))*};
$tag_regex5 = qq{$comment_tag_regex|<$tag_regex_>};

{
	local $/;
	open(FH,"sample.html") or die "cannot open file:$!";
	$html = <FH>;
	close FH;
}
timethese(100,{
		'TEST1' => '&test1;',		#regex1 オリジナル
		'TEST2' => '&test2;',		#regex2 括弧有り
		'TEST3' => '&test3;',		#regex3 括弧無し
		'TEST4' => '&test4;',		#regex4 括弧有り変形
		'TEST5' => '&test5;',		#regex5 括弧無し変形
	}
);
exit;

sub test1 {
	@data = $html =~ /$tag_regex1/g;
}
sub test2 {
	@data = $html =~ /$tag_regex2/g;
}
sub test3 {
	@data = $html =~ /$tag_regex3/g;
}
sub test4 {
	@data = $html =~ /$tag_regex4/g;
}
sub test5 {
	@data = $html =~ /$tag_regex5/g;
}

 内容は大したこと無い。いくつかの正規表現でタグを取り出し、それに掛かった時間を調べる物。まあ @data が全て同じになるわけではないが、while で $& を取り出せば同じ物が出てくる。

 サンプルとして使った HTML ファイルは約137KBでタグの総数は3661個。

 結果はどうなったかというと以下の通り。

Benchmark: timing 100 iterations of TEST1, TEST2, TEST3, TEST4, TEST5...
     TEST1:  2 wallclock secs ( 1.73 usr +  0.00 sys =  1.73 CPU) @ 57.67/s (n=100)
     TEST2:  5 wallclock secs ( 4.97 usr +  0.00 sys =  4.97 CPU) @ 20.12/s (n=100)
     TEST3:  3 wallclock secs ( 2.89 usr +  0.00 sys =  2.89 CPU) @ 34.59/s (n=100)
     TEST4:  4 wallclock secs ( 3.77 usr +  0.00 sys =  3.77 CPU) @ 26.55/s (n=100)
     TEST5:  2 wallclock secs ( 2.44 usr +  0.00 sys =  2.44 CPU) @ 41.02/s (n=100)

 思った以上に差が…。TEST1 が最初に使っていたPerlメモに載っていた物。TEST2〜TEST5 は、一応自分で書いた物。

 TEST2 の物を convTag.pm で使用している。TEST1 の35%弱しか速度が出ていないことになる。

 TEST3 は TEST2 から変数へ取り込む為の括弧を取り除いた。これで TEST1 の60%弱の速度が出ている。TEST2 と比べると約1.7倍という計算になる。括弧ってそんなに遅いのか…。

 TEST4 は TEST2 の一部を変形した物。これで TEST1 の46%程度になった。TEST2 と比較すると約1.3だ。

 TEST5 は TEST3 の一部を変形した物。ようやく TEST1 の約71%。TEST3 の1.2倍弱。

 TEST2 と TEST4 に関してはタグ内の要素を取り出す処理の一部を既に行っているため、他のテストとは単純に比較することはできない。比較するならば convTag 関数で行っている事を両方の正規表現で実装し、その状態でしなければならない。

 というわけで、ここで単純に比較するとなると TEST1 と TEST5 だ。3661個を抜き出す処理を100回。マッチしたのは結局366100回。これだけの試行回数で差が1秒前後ならば、まあいいんじゃなかろうか。IEとほぼ同じと思われる解釈もしてるわけだし。まあほとんどの場合、ちゃんとした書式で書かれたタグが入力されるんだろうけど…。

 なんで convTag.pm で使っていた正規表現を変形しようと思ったかというとそれはPerlメモ

これを Jeffrey E. F. Friedl氏原著による「詳説 正規表現 ( Mastering Regular Expressions )」で「ループ展開」として書かれている手法で実行速度を速くしたものが最初のスクリプト正規表現です.簡単なベンチマークをとってみたところ約 1.5倍ほど速かったです.

 と書かれていたため。それならばどれぐらい変わるものだろうかと言うことでやり始めたんだけど、まあ誌面も尽きてきたことだし(?)それに関してはまた今度ということで。

 実はこのループ展開の本質というのをまだ理解していないため、今回書いた変形も恐らくループ展開の手法とは違うと思うし、以降書くつもりの物についてもそれがほんとにそうなのかは自信がなかったりする…。

 というかね、ループ展開について書かれたページがぐぐっても全然出てこない。逆にこっちが「ループ展開ってなんなの?」と聞きたいぐらいで。

 とりあえず今回はこれまで。

 んじゃ。

*1:太陽誘電は別格です