foreach と map を比べてみた

perlの話です。

文字列1と文字列2を比較して、文字列2に文字列1の文字が全て含まれているか確認する処理をperlで書いてる最中に、「foreachとmapのどちらで書いた方が早いのか」と急に気になったのでベンチマークして比較してみました。

つまりこんなような処理をしたときに・・・

my $count1 = 'abcdefghijklmnopqrstuvwxyz';
my $count2 = 'bzdfijlmonqrsvaehykcwgptux';

$count2 =~ s/$_// foreach( split("", $count1) ); # ここでmapを使うべきか?

上記foreachを使っている箇所をmapにした方が高速化が望めるのかを確認してみたくなったわけです。


こんな感じでBenchmarkモジュールを使って比較してみます。

#!/usr/local/bin/perl5.14.3

use strict;
use warnings;

use Benchmark qw(cmpthese);

my $count1 = 'abcdefghijklmnopqrstuvwxyz';
my $count2 = 'bzdfijlmonqrsvaehykcwgptux';

my $code = {
        'foreach'       =>      q|$count2 =~ s/$_// foreach(split("", $count1));|,
        'map'           =>      q|map { $count2 =~ s/$_//; } split("", $count1);|,
};

my $iterate = 10_000_000;

cmpthese($iterate, $code);


で、結果を確認してみたところ・・・

# ./bench_foreach.pl
             Rate foreach     map
foreach 2191781/s      --    -72%
map     7901235/s    260%      --


mapの方が相当早いようですね。差が開くのではないかとは思っていましたが、まさか250%近く差が出るとは思ってもみませんでした。foreachってもしかして今回のような書き方だとイテレーションごとにsplitを実行してるのかな・・・?
というわけで、ベンチマークするコードに「既にsplitで分離し終わったリストを使うforeach」を追加してテストしてみます。

my $count1 = 'abcdefghijklmnopqrstuvwxyz';
my $count2 = 'bzdfijlmonqrsvaehykcwgptux';
my @count3 = split("", $count1);

my $code = {
        'foreach'       =>      q|$count2 =~ s/$_// foreach(split("", $count1));|,
        'map'           =>      q|map { $count2 =~ s/$_//; } split("", $count1);|,
        'splitted'      =>      q|$count2 =~ s/$_// foreach(@count3);|,
};
# ./bench_foreach.pl
              Rate  foreach splitted      map
foreach  2180579/s       --     -59%     -73%
splitted 5267490/s     142%       --     -35%
map      8050314/s     269%      53%       --


うーん・・・foreachの中でsplitを実行しているコードよりは早くなったようですが、それでもmapには勝てないようです。速度を求めるならmapを使った方がよさそうですね。



折角なんでmapの方でも分離し終わったリストを使ってみましょう。

my $code = {
        'foreach'       =>      q|$count2 =~ s/$_// foreach(split("", $count1));|,
        'map'           =>      q|map { $count2 =~ s/$_//; } split("", $count1);|,
        'splitted'      =>      q|$count2 =~ s/$_// foreach(@count3);|,
        'splitted_map'  =>      q|map { $count2 =~ s/$_//; } @count3|,
};
# ./bench_foreach.pl
                   Rate      foreach     splitted          map splitted_map
foreach       2173175/s           --         -60%         -70%         -79%
splitted      5446809/s         151%           --         -25%         -47%
map           7272727/s         235%          34%           --         -30%
splitted_map 10322581/s         375%          90%          42%           --

やはりsplitted_mapの方が早いですね。30回程度スクリプトを実行してみましたが、大体20~150%程度の差が出るようです。
(修正: 2013/06/21 16:00 最後のベンチマーク結果がこの記事に記載されているものと別のスクリプトを実行したものだったので修正)