古いスクリプトを見直してみる - 1 -

最近、ちゃんとしたPerlの本を読み始めたので古いスクリプトを書き直して実行速度と読みやすさを調べてみた。
第一弾はISBN10からISBN13への変換ルーチン

実際のコード

  • 最初に書いたサブルーチン(を多少書き直したもの)
#!/usr/bin/perl
use lib '/home/natu-n/perl/lib/perl5/site_perl/5.8.8';
use lib '/home/natu-n/local/lib/perl5';
use strict;
use warnings;
use Readonly;
use List::Util qw( sum );
use List::MoreUtils qw( pairwise );
use Benchmark;
Readonly my $BLANK => q{};
Readonly my @ISBN13_CD   => qw( 0 9 8 7 6 5 4 3 2 1 );
Readonly my @ISBN13_MULT => qw( 1 3 1 3 1 3 1 3 1 3 1 3 0 );

#中略

sub cvISBN13_1 {
    my $temp = shift;
    my @w_isbn = split($BLANK, "978" . $temp);
    my $odd  = 0;
    my $even = 0;
    for ( my $i = 0; $i < 11; $i += 2 ) {
        $odd += $w_isbn[$i];
    }
    for ( my $i = 1; $i < 12; $i += 2 ){
        $even += $w_isbn[$i];
    }
    $even = $even * 3;
    my $mod = ($odd + $even) % 10;
    if ($mod != 0) {
        $mod = 10 - $mod;
    }
    $w_isbn[12] = $mod;
    return join $BLANK, @w_isbn;
}

奇数桁、偶数桁を別々にインデックスを2ずつ加算しながら合計
偶数桁計を3倍してから奇数桁計と合算、10で割った余りを10から引いた値(ただし余が0の場合は0のまま)をチェックデジットとする

  • 一回目の見直し版
#先頭の宣言部分は同じ

sub cvISBN13_2 {
    my $temp = shift;
    my @w_isbn=split($BLANK, '978' . $temp);
    my $mod  = 0;
    for my $ix (0..11) {
        $mod += ($ix % 2) ? $w_isbn[$ix] * 3
                          : $w_isbn[$ix]
                          ;
    };
    $w_isbn[12] = $ISBN13_CD[$mod % 10];
    return join $BLANK, @w_isbn;
}

ループの回数を判断なしで決め打ち、偶数桁の場合は3倍してから合算まで済ます
余は求めずに、チェックデジットのテーブルを使用する

  • 二回目の見直し版
#先頭の宣言部分は同じ

sub cvISBN13_3 {
    my $temp = shift;
    my @w_isbn=split($BLANK, '978' . $temp);
    my $sum = sum (pairwise { $a * $b } @w_isbn, @ISBN13_MULT);
    $w_isbn[12] = $ISBN13_CD[$sum % 10];
    return join $BLANK, @w_isbn;
}

ゴリゴリとループを書かずにユーティリティを使い、桁により3倍(もしくは1倍)して合計を求めるまでを一気に済ます

ベンチマーク

  • テストコード
#先頭の宣言部分は同じ

my $isbn  = 4798109401;
my $count = 10_000;

timethese($count,
    {
    'TEST1' => "&cvISBN13_1($isbn)",
    'TEST2' => "&cvISBN13_2($isbn)",
    'TEST3' => "&cvISBN13_3($isbn)",
    }
);
  • 結果
Benchmark: timing 10000 iterations of TEST1, TEST2, TEST3...
     TEST1:  0 wallclock secs ( 0.26 usr +  0.00 sys =  0.26 CPU) @ 38787.88/s (n=10000)
     TEST2:  0 wallclock secs ( 0.29 usr +  0.00 sys =  0.29 CPU) @ 34594.59/s (n=10000)
     TEST3:  1 wallclock secs ( 0.85 usr +  0.00 sys =  0.85 CPU) @ 11743.12/s (n=10000)

元々のコードと一回目の書き直し版があまり変化が無く、また、最後のが残念な結果に><
ちなみにこんなコードも考えてみた(一番無駄がないかも?)

#先頭の宣言部分は同じ

sub cvISBN13_4 {
    my $temp = shift;
    my @w_isbn=split($BLANK, '978' . $temp);
    my $sum = sum (@w_isbn[0, 2, 4, 6, 8, 10])
        + ( 3 * sum (@w_isbn[1, 3, 5, 7, 9, 11]) );
    $w_isbn[12] = $ISBN13_CD[$sum % 10];
    return join $BLANK, @w_isbn;
}
Benchmark: timing 10000 iterations of TEST4...
     TEST4:  1 wallclock secs ( 0.27 usr +  0.00 sys =  0.27 CPU) @ 37647.06/s (n=10000)

まぁ10,000回回した結果なので実際にはどれでも好きなものを使えばいいのかも

備考

明日はISBNのハイフン編集のところ