Arbitrary controls of benchmarks

私はよくPerlスクリプトベンチマークを記事にするが,ベンチマークスクリプトを書くのもその結果を評価するのも実は非常に難しいことに気付いた。そこでここでは,ベンチマークについての覚書を記しておく。
まず,ベンチマークの結果はある程度恣意的に操作できる。ベンチマークの結果を操作する方法はいくつかあるのだが,まず測定したいコードがベンチマークコード全体に占める割合を操作するのが簡単であろう。
たとえば,先日の変数エイリアスについてのベンチマークは,エイリアスは代入よりも25%ほど高速であるという結果となった。
そのコードについては,エイリアス/代入の回数を増やすことで,差を大きくすることができる。

#!perl -w
use strict;
use Benchmark qw(:all);
use Scalar::Alias;
print "For string\n";
my @strings = (('foo') x 100);
cmpthese -1 => {
	alias => sub{
		for my $i(@strings){
			my alias $a = $i;
			my alias $b = $i;
			my alias $c = $i;
			my alias $d = $i;
			my alias $e = $i;
			my alias $f = $i;
			my alias $g = $i;
			my alias $h = $i;
			my alias $i = $i;
			my alias $j = $i;
		}
	},
	assign => sub{
		for my $i(@strings){
			my $a = $i;
			my $b = $i;
			my $c = $i;
			my $d = $i;
			my $e = $i;
			my $f = $i;
			my $g = $i;
			my $h = $i;
			my $i = $i;
			my $j = $i;
		}
	},
};
__END__

結果:

For string
         Rate assign  alias
assign 4443/s     --   -47%
alias  8453/s    90%     --

するとこのように,エイリアスは代入より約2倍の速度であるという結果になる。この結果の評価は難しい。これは実際には,もともとのコードよりも純粋にエイリアスと代入の差を計っていると言えるので,こちらのほうが正確であるともいえる。しかしその一方で,測定コードは非常に不自然である。
逆に,次に示すコードのように,全コード中の真に測りたいコードの割合を減らすと,差はほとんどないようにも見える。

#!perl -w
use strict;
use Benchmark qw(:all);
use Scalar::Alias;
sub aliased{
	my alias $x = shift;
	$x =~ /foo/;
	return;
}
sub no_aliased{
	my $x = shift;
	$x =~ /foo/;
	return;
}
print "For string\n";
my @strings = (('foo') x 10);
cmpthese -1 => {
	alias => sub{
		for my $i(@strings){
			aliased($i);
		}
	},
	assign => sub{
		for my $i(@strings){
			no_aliased($i);
		}
	},
};
__END__

結果:

For string
           Rate assign  alias
assign  98248/s     --    -7%
alias  105218/s     7%     --

これはコード自体は先の2倍差があるコードよりは自然に見えるかもしれないが,測定したいコードそれ自体については意味がある結果とはいいがたい。しかし,実際のスクリプトにおける効果という意味ではこちらのほうが正確かもしれないのだ。

これらの結果では2倍差が測定したいコードそのものについては正確だが,だからといってエイリアスを使うと劇的にスクリプトの性能が向上するとは限らない。むしろ,実際のスクリプトでは,代入(値のコピー)がボトルネックになっているようなスクリプトでないかぎり,それほど差は出ないと思われる。その意味では,差が7%しかない方のスクリプトのほうが情報としての価値がある可能性もある。

ベンチマークは難しい。