AUTOLOADとシンボルテーブルに関するパフォーマンステスト

はじめに

最近、前に買った「オブジェクト指向 Perlマスターコース」をもう一度読み直しています。この本を読んで、AUTOLOADを使うとコストがかかることは理解しました。でも実際にAUTOLOADを多用したことがないので、どれくらい遅くなるのかという感覚を持っていません。そこでAUTOLOADを下手に使った場合と、シンボルテーブルを活用して賢くAUTOLOADを使った場合のパフォーマンスの差を比べてみました。実験の結果、確かに両者にはパフォーマンスに差が出ることはがわかりました。

実験

今回の実験では、本当に単純なクラス(AutoloadLazy、AutoloadSmart)を定義して利用します。両クラスは、外から見た時の振る舞いは同じですが、内部構造が若干異なります。両者ともクラス内には_alphaというプライベートな属性があるだけで、それに対する静的なアクセッサーは提供されていません。get_alphaというメソッドが呼び出されると、AUTOLOADが実行され、_alphaの値が返されるようになっています。AutoloadLazyクラスでは、get_alphaメソッドを呼び出す毎にAUTOLOADが実行されます。一方、AutoloadSmartクラスではシンボルテーブルを活用してAUTOLOADの実行は、get_alphaメソッドをはじめて呼び出した時だけになるように実装されています。

以下の3つのコードを用意しました。

  • autoload_test.pl: Benchmarkモジュールを用いてパフォーマンステストを実行
  • AutoloadLazy.pm: AUTOLOADを無駄に呼び出す下手な方法
  • AutoloadSmart.pm: AUTOLOADの呼び出しが1回になるようにシンボルテーブルを活用する賢い方法

ちなみに実験した環境は以下の通りです。

[ysano@fedora]~/perl/autoload_test% cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 15
model           : 2
model name      : Intel(R) Pentium(R) 4 CPU 2.66GHz
stepping        : 9
cpu MHz         : 2657.239
cache size      : 512 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 2
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe cid xtpr
bogomips        : 5259.26

autoload_test.pl

use strict;
use warnings;
use Benchmark;
use AutoloadLazy;
use AutoloadSmart;

my $l = AutoloadLazy->new(alpha => 'lazy way');
my $s = AutoloadSmart->new(alpha => 'smart way');

print $l->get_alpha() . "\n"; 
print $s->get_alpha() . "\n"; 

Benchmark::timethis(1000000, sub { $l->get_alpha });
Benchmark::timethis(1000000, sub { $s->get_alpha });

AutoloadLazy.pm

package AutoloadLazy;
use strict;
use warnings;
use vars '$AUTOLOAD';

sub new
{
    my ($class, %args) = @_;
    bless {
        _alpha => $args{alpha} || "???",
    }, $class;
}

sub AUTOLOAD
{
    my ($self) = @_;

    if ($AUTOLOAD =~ /.*::get(_\w+)/) {
        my $attr_name = $1;
        return $self->{$attr_name};
    }
}

sub DESTROY {}

1;

AutoloadSmart.pm

package AutoloadSmart;
use strict;
use warnings;
use vars '$AUTOLOAD';

sub new
{
    my ($class, %args) = @_;
    bless { 
        _alpha => $args{alpha} || "???",
    }, $class; 
}

sub AUTOLOAD
{
    no strict 'refs'; 
    my ($self) = @_;

    print "AUTOLOAD is called ($AUTOLOAD)\n";

    if ($AUTOLOAD =~ /.*::get(_\w+)/) {
        my $attr_name = $1;
        *{$AUTOLOAD} = sub { return $_[0]->{$attr_name} };
        return $self->{$attr_name};
    }
}

sub DESTROY {}

1;

実験結果

[ysano@fedora]~/perl/autoload_test% perl autoload_test.pl
lazy way
AUTOLOAD is called (AutoloadSmart::get_alpha)
smart way
timethis 1000000:  6 wallclock secs ( 6.70 usr +  0.00 sys =  6.70 CPU) @ 149253.73/s (n=1000000)
timethis 1000000:  1 wallclock secs ( 1.12 usr +  0.00 sys =  1.12 CPU) @ 892857.14/s (n=1000000)

両者の間に約6倍のパフォーマンスの差が出ました。「オブジェクト指向 Perlマスターコース」で説明されているように、確かにAUTOLOADのオーバーヘッドはそれなりに大きいことがわかりました。

おわりに

「AUTOLOADのオーバーヘッドの測定なんて対象としているシステムの全体の中で測定しないと意味が無い」と言われるかもしれません。これはパフォーマンスチューニングの基本ですね。まぁ確かにそうですが、私の関心としてAUTOLOADの実行が重いんだなということが体感したかっただけなので、良しとさせて下さい。

参考文献

オブジェクト指向Perlマスターコース―オブジェクト指向の概念とPerlによる実装方法

オブジェクト指向Perlマスターコース―オブジェクト指向の概念とPerlによる実装方法