switch文とif連鎖の比較

Perl 5.10.0でswitch文が導入されたことにより,ifの連鎖はもっとわかりやすい構文に書き換えられるようになった。

use feature 'switch';
given($foo){
  when("bar"){ ... }
  when("baz"){ ... }
  default{     ... }
}

switch文の導入で便利になった一方,このwhen()で使われるマッチングは「スマートマッチ」という特殊な演算子によって行われることのコストが懸念される。
そこで,switch文とif連鎖の速度を比較してみた。
また,かつて単純なif連鎖とよく比較されたのは,ハッシュテーブルを参照する方式である。if連鎖の複雑さがO(n)なのに対し,ハッシュテーブルの参照はO(1)であるため,連鎖が長くなるとハッシュテーブルの方が高速であるとされる。『Perlベストプラクティス』では,ハッシュテーブルのほうが保守性もよいため,if連鎖は極力避けるべきだと提唱している。そこで,今回のベンチマークではハッシュテーブルの参照も考慮に入れた。
結果は上から,(1)平均的なケース,(2)if連鎖が有利なケース,(3)if連鎖が不利なケースとなっている。
結果(perl 5.10.0 linus multi-threaded -DDEBUGGING):

(1)
            Rate   switch if-chain    table
switch   14355/s       --     -11%     -12%
if-chain 16144/s      12%       --      -1%
table    16290/s      13%       1%       --
(2)
            Rate   switch    table if-chain
switch   15858/s       --      -4%     -11%
table    16439/s       4%       --      -7%
if-chain 17772/s      12%       8%       --
(3)
            Rate   switch if-chain    table
switch   13524/s       --     -13%     -17%
if-chain 15605/s      15%       --      -4%
table    16291/s      20%       4%       --

これをみると,if連鎖とテーブル参照は差がなく,switch文はその他二つより常に遅いようである。意外なことに,10個程度の連鎖では特にテーブル参照が高速ともいえないようだ。

ベンチマークスクリプト*1

#!perl -w
use strict;
use Benchmark qw(:all);

sub do_something{
    my $sum = 0;
    for my $i(1 .. 5){
        $sum += $i;
    }
}

my @procs = qw(cat dog rat mouse moose squirrel zebra bison yak python);

my %tab = map{ $_ => sub{ do_something() } } @procs;

my $i = 0;
for my $x([@procs, @procs], [@procs, ($procs[0]) x @procs], [@procs, ($procs[-1]) x @procs]){
    my @ary = @{$x};
    $i++;
    print "($i)\n";

    cmpthese -1 => {
        'table' => sub{
            for my $proc(@ary){
                $tab{$proc}->();
            }
        },
        'if-chain' => sub{
            for my $proc(@ary){
                if($proc eq 'cat'){
                    do_something();
                }
                elsif($proc eq 'dog'){
                    do_something();
                }
                elsif($proc eq 'rat'){
                    do_something();
                }
                elsif($proc eq 'mouse'){
                    do_something();
                }
                elsif($proc eq 'moose'){
                    do_something();
                }
                elsif($proc eq 'squirrel'){
                    do_something();
                }
                elsif($proc eq 'zebra'){
                    do_something();
                }
                elsif($proc eq 'bison'){
                    do_something();
                }
                elsif($proc eq 'yak'){
                    do_something();
                }
                elsif($proc eq 'python'){
                    do_something();
                }
                else{
                    die $proc;
                }
            }
        },
        switch => sub{
            for (@ary){
                use feature 'switch';

                when('cat'){
                    do_something();
                }
                when('dog'){
                    do_something();
                }
                when('rat'){
                    do_something();
                }
                when('mouse'){
                    do_something();
                }
                when('moose'){
                    do_something();
                }
                when('squirrel'){
                    do_something();
                }
                when('zebra'){
                    do_something();
                }
                when('bison'){
                    do_something();
                }
                when('yak'){
                    do_something();
                }
                when('python'){
                    do_something();
                }
                default{
                    die $_;
                }
            }
        }
    };
}

*1:do_something()は「何らかの処理をする」程度の意味で,現実のスクリプトで行われるのは単純な代入文などである。