定数関数の威力

JPerl Advent Calendar 2008 第一日目でも紹介されているように,特殊な方法で宣言した関数*1は定数関数として特別に処理され,コンパイル時に展開される。

しかし,定数の展開はコンパイル時にしか行われない。つまり,実行時に呼び出す実体が決まるメソッドでは,たとえ実質的に定数であっても定数の展開は行われない。

それでは定数を返すメソッドを定数関数にする意味はないのかというと,実はそうでもない。定数関数は特殊な形式でコンパイルされる*2ので,メソッドとして呼び出しても速い。

ベンチマークの結果は以下の通り。
method: 通常のメソッド呼び出し,func: 通常の関数呼び出し,const_m: 定数メソッドの呼び出し,hashelem: ハッシュリファレンスの要素参照,const_f: 定数関数呼び出し(実質的に定数と同じ)。

Perl 5.8.9 on linux
            Rate   method     func  const_m hashelem  const_f
method   12560/s       --      -8%     -54%     -55%     -78%
func     13653/s       9%       --     -50%     -51%     -76%
const_m  27049/s     115%      98%       --      -3%     -53%
hashelem 27927/s     122%     105%       3%       --     -52%
const_f  57961/s     361%     325%     114%     108%       --

このように,定数関数には及ばないものの,普通のメソッド呼び出しと比べると2倍近く高速であり,ハッシュリファレンスの要素参照に匹敵する。

ベンチマークスクリプト

#!perl -w

use strict;
use Benchmark qw(:all);

printf "Perl %vd on $^O\n", $^V;

BEGIN{
  package Foo;

  sub method{ 42 }
  *const_method = sub(){ 42 };

  sub new{
    my $class = shift;
    bless {@_}, $class;
  }
}

#use Devel::Peek; Dump(Foo->can('const'));

my $o = Foo->new(hash_elem => 42);

sub func{ 42 }
sub const_func(){ 42 }

my $value = 42;
cmpthese -1 => {
  method  => sub{
    for(1 .. 100){
      $o->method == $value or die;
    }
  },
  const_m  => sub{
    for(1 .. 100){
      $o->const_method == $value or die;
    }
  },
  hashelem => sub{
    for(1 .. 100){
      $o->{hash_elem} == $value or die;
    }
  },
  func => sub{
    for(1 .. 100){
      func() == $value or die;
    }
  },
  const_f => sub{
    for(1 .. 100){
      const_func() == $value or die;
    }
  },
};
__END__

*1:constant.pmを使うと簡単に作ることができる。

*2:内部的には特殊なXSUBになる。