2009-01-15
List::Util::firstは遅い
(追記:この用途ではList::Util::firstを使うのは誤りで,List::MoreUtils::anyが意図されたコードです。効率についての結論は変わりません)
List::Util::first{expr}は組み込みのgrep{expr}に似ているが,exprが最初に真になった段階でその値を返すので,grep{expr}よりも効率がいいと説明されることが多い。しかし,実際にベンチマークを取ってみると,多くの場合grep{expr}より遅い。最初の要素が真になるというfirst{expr}にとって最適な条件でさえ,要素数が40を越えたあたりでようやくgrep{expr}とほぼ同程度の速度になる。したがって,List::Util::first{expr}が効果的なケースはそれほど多くないと思われる。
また,もし単なる文字列の検索でよく,その検索をプログラム中で繰り返すならば,grep{expr}よりもハッシュを見るほうが常に圧倒的に速い。
$ perl first_benchmark.pl 40
Perl 5.8.5 on linux.
Found in the frist element:
Rate grep first hash
grep 11377/s -- -9% -96%
first 12444/s 9% -- -95%
hash 262044/s 2203% 2006% --
Not found:
Rate first grep hash
first 4483/s -- -61% -99%
grep 11487/s 156% -- -96%
hash 309688/s 6807% 2596% --
ベンチマークスクリプト(最初の引数で要素数を指定できる。デフォルトは10):
#!perl -w use strict; use Benchmark qw(:all); use List::Util qw(first); printf "Perl %vd on %s.\n", $^V, $^O; my $n = shift @ARGV; my @values = ('foobar', ('xxxxxx') x ($n || 10)); my %values_in; @values_in{@values} = (); print "Found in the frist element:\n"; my $s = $values[0]; cmpthese -1, { grep => sub{ for( 1 .. 10 ){ 1 if grep { $_ eq $s } @values; } }, first => sub{ for( 1 .. 10 ){ 1 if first { $_ eq $s } @values; } }, hash => sub{ for( 1 .. 10){ 1 if exists $values_in{ $s }; } }, }; print "Not found:\n"; $s = reverse $s; cmpthese -1, { grep => sub{ for( 1 .. 10 ){ 1 if grep { $_ eq $s } @values; } }, first => sub{ for( 1 .. 10 ){ 1 if first { $_ eq $s } @values; } }, hash => sub{ for( 1 .. 10){ 1 if exists $values_in{ $s }; } }, }; __END__


