each()は遅い上に微妙な問題も起きやすい
特別な条件がないかぎり、each()は使うべきではありません。代わりにkeys()/values()を使うべきです。その理由は2つあります。
each()は遅い
each()でハッシュ全体をループするのは遅いです。これは、keys()/values()がその内部の値をそのまま参照する*1のに対し、each()は代入しないとその値を使えないからです。
ベンチマーク:
#!perl use strict; use warnings; use Benchmark qw(cmpthese); my %hash = map { $_ => $_ } ( 1 .. 10000 ); cmpthese -1, { each_k => sub { while(my $key = each %hash) { } }, each_kv => sub { while(my($key, $value) = each %hash) { } }, keys => sub { foreach my $key(keys %hash) { } }, values => sub { foreach my $value(values %hash) { } }, }; __END__
結果*2:
$ perlbrew exec perl each-bench.pl perl-5.12.3 ========== Rate each_kv each_k keys values each_kv 57.1/s -- -20% -56% -81% each_k 71.7/s 25% -- -44% -77% keys 129/s 126% 80% -- -58% values 307/s 438% 329% 138% -- perl-5.14.0 ========== Rate each_kv each_k keys values each_kv 57.7/s -- -20% -55% -79% each_k 71.7/s 24% -- -44% -74% keys 128/s 121% 78% -- -53% values 274/s 375% 282% 115% -- perl-5.8.9 ========== Rate each_kv each_k keys values each_kv 55.4/s -- -21% -57% -83% each_k 70.3/s 27% -- -46% -79% keys 130/s 134% 85% -- -60% values 328/s 492% 367% 153% --
ラクダ本などでは「膨大な数のエントリを持つtieされたハッシュ(DBMなど)でkeys()/values()を行うとメモリを大量に消費するからeach()を使うほうが空間効率が良い」との記述がありますが、これは特殊なケースです。時間効率の観点からみるとeach()を使う理由はありません。
each()ループを中断すると内部イテレータの不整合が起きる
each()はハッシュ毎に内部イテレータを使用してループを行いますが、これはループをlast()などで中断してもリセットされません。リセットするためには明示的にkeys()/values()を呼び出す必要があります。
# linear search my $found; while( my $k = each %hash) { if($k eq 'foo') { $found = $k; last; } } keys %hash; # reset its internal iterator
この内部イテレータの不整合によって非常に分かりにくいバグを生むことが知られています。私も何度かそういったバグを経験しましたし、Perlレベルではなるべくeach()を避けたほうがいいでしょう*3。
追記: