2009年03月29日 23:45 [Edit]

perl - for(;;)よりforeach

最近のid:naoyaのソースがすごく気になったので。


何が気になるかというと、for(;;)の利用。それもCやJavaScriptなど、事実上それしかないソースからそのまま転写したとかならとにかく、編集距離 (Levenshtein Distance) - naoyaのはてなダイアリーでは Python版がちゃんとxrangeを使っているのにPerl版がfor(;;)のでますます解せない。

"Perl Best Practices"でも、読みやすさの観点からCスタイルのforは避けよ(pp. 100-101)と言っているが、もう一つ損なわれるものがある。速度である。

#!/usr/bin/env perl
use strict;
use warnings;
use Benchmark qw/cmpthese timethese/;

for my $e ( 0 .. 6 ) {
    my $n = 10**$e;
    print "#### loop $n times\n";
    cmpthese(
        timethese(
            0,
            {
                for => sub {
                    my $dummy;
                    for ( my $i = 0 ; $i < $n ; $i++ ) { $dummy += $i }
                },
                foreach => sub {
                    my $dummy;
                    foreach my $i ( 0 .. $n - 1 ) { $dummy += $i }
                  }
            }
        )
    );
}

上はループ回数1回から100万回までベンチマークするスクリプトであるが、以下の結果を見てほしい。

#### loop 1 times
            Rate foreach     for
foreach 466071/s      --    -50%
for     929903/s    100%      --
#### loop 10 times
            Rate     for foreach
for     223832/s      --    -11%
foreach 250507/s     12%      --
#### loop 100 times
           Rate     for foreach
for     27522/s      --    -40%
foreach 45676/s     66%      --
#### loop 1000 times
          Rate     for foreach
for     2898/s      --    -45%
foreach 5238/s     81%      --
#### loop 10000 times
         Rate     for foreach
for     289/s      --    -45%
foreach 527/s     82%      --
#### loop 100000 times
          Rate     for foreach
for     28.5/s      --    -43%
foreach 50.5/s     77%      --
#### loop 1000000 times
          Rate     for foreach
for     2.24/s      --    -40%
foreach 3.70/s     66%      --

ループ一回の場合を除き、すべてforeachが勝っており、それもかなり無視できない差である。ここまで差があるとは私も思わなかった。

ちなみに1..1e6とかやっても、foreachでは百万要素の配列を作ったりしないので安心していい。これは「404 Blog Not Found:perl - for(1..1e10) と Iterator」でも以前指摘している。

さらに付け加えると、今回のlevenshtein_distance()では、substr()を使えば文字ばらしの必要はなく、かえってすっきりと書くことが出来し。二次元配列は$m->[$x][$y]よりも$m[$x][$y]とレファランスを一つ減らした方がわずかではあるが速く、可読性もあまり失われない。以下、実例。

sub levenshtein_distance {
    my ( $s1, $s2 ) = @_;
    my @m;
    $m[$_][0] = $_ for ( 0 .. length $s1 );
    $m[0][$_] = $_ for ( 0 .. length $s2 );
    for my $i ( 1 .. length $s1 ) {
        for my $j ( 1 .. length $s2 ) {
            my $diff =
               substr( $s1, $i - 1, 1 ) eq substr( $s2, $j - 1, 1 ) ? 0 : 1;
            $m[$i][$j] = min(
                $m[ $i - 1 ][ $j - 1 ] + $diff,
                $m[ $i - 1 ][ $j     ] + 1,
                $m[ $i     ][ $j - 1 ] + 1
            );
        }
    }
    return $m[-1][-1];
}

Dan the Perl Monger


この記事へのトラックバックURL

この記事へのコメント
substr()じゃなくばらしてあるのは、
一般的なオブジェクトの配列も意識してあるからだと思います。
Posted by あああ at 2009年03月30日 06:37
しかしまあ(どっちにしても、なんだが)あらためて並べてみるとPerlのコードってえのはキッタねえなあ。
Posted by shou at 2009年03月30日 14:03
相変わらず汚くて吐き気がしますね(苦笑)
Posted by b at 2009年03月30日 18:14
あーあ怒らせてしまいましたね
ttp://naoya.g.hatena.ne.jp/naoya/20090330/1238367025
Posted by a at 2009年03月31日 20:43