馬鹿にできないアクセサのオーバーヘッド
Moose は has でアクセサ定義できるので便利なのですが、アクセサは要はサブルーチンコールなので、パフォーマンス気にして作っているサービスではオーバーヘッドが気になるなぁと思い、ちょっとベンチ取ってみました。
ベンチマーク対象は、Moose で作成したクラスと、Class::Accsessor::Fast で作成したクラスの、アクセサ経由とオブジェクト直でのデータ取得です。ベンチマークコードは以下です。
#!/usr/bin/perl package ClassMoose; use Moose; has 'ro1' => ( is => 'ro', isa => 'Bool', default => 1 ); has 'ro2' => ( is => 'ro', isa => 'Int', default => 1 ); has 'ro3' => ( is => 'ro', isa => 'Str' , default => '1' ); has 'ro4' => ( is => 'ro', isa => 'ArrayRef' , default => sub { [] }); has 'ro5' => ( is => 'ro', isa => 'HashRef' , default => sub { {} }); has 'ro6' => ( is => 'ro', isa => 'CodeRef' , default => sub { sub {1} }); has 'rw1' => ( is => 'rw', isa => 'Bool', default => 1 ); has 'rw2' => ( is => 'rw', isa => 'Int', default => 1 ); has 'rw3' => ( is => 'rw', isa => 'Str' , default => '1' ); has 'rw4' => ( is => 'rw', isa => 'ArrayRef' , default => sub { [] }); has 'rw5' => ( is => 'rw', isa => 'HashRef' , default => sub { {} }); has 'rw6' => ( is => 'rw', isa => 'CodeRef' , default => sub { sub {1} }); no Moose; __PACKAGE__->meta->make_immutable; package ClassCAF; use strict; use warnings; use base 'Class::Accessor::Fast'; __PACKAGE__->mk_accessors(qw/rw1 rw2 rw3 rw4 rw5 rw6/); package main; use strict; use warnings; use Benchmark ':all'; my $m = ClassMoose->new; my $c = ClassCAF->new; no warnings 'void'; cmpthese(timethese(300000, { m_ro => sub { $m->ro1; $m->ro2; $m->ro3; $m->ro4; $m->ro5; $m->ro6; }, m_rw => sub { $m->rw1; $m->rw2; $m->rw3; $m->rw4; $m->rw5; $m->rw6; }, m_direct => sub { $m->{'rw1'}; $m->{'rw2'}; $m->{'rw3'}; $m->{'rw4'}; $m->{'rw5'}; $m->{'rw6'}; }, c_rw => sub { $c->rw1; $c->rw2; $c->rw3; $c->rw4; $c->rw5; $c->rw6; }, c_direct => sub { $c->{'rw1'}; $c->{'rw2'}; $c->{'rw3'}; $c->{'rw4'}; $c->{'rw5'}; $c->{'rw6'}; }, }));
結果は以下です。
Rate m_ro m_rw c_rw m_direct c_direct m_ro 67265/s -- -0% -17% -82% -87% m_rw 67416/s 0% -- -17% -82% -87% c_rw 81301/s 21% 21% -- -78% -85% m_direct 365854/s 444% 443% 350% -- -32% c_direct 535714/s 696% 695% 559% 46% --
Moose ではアクセサの ro と rw の差は出ませんでした。アクセサ経由ではやはり、Class::Accessor::Fast ベースのクラスの方が小さい分 1.2 倍高速です。ダイレクトアクセスではやはり、アクセサ経由と雲泥の差ですね。
このベンチマーク結果を見ると、やはりアクセサ経由のデータアクセスは減らした方が良い事が分かります。
アクセサ経由のデータアクセスを減らすひとつの方法として、ひとつ例を挙げるとしたら以下の例があります。
まず、このようなコードがあったとします。
sub no_tmp { warn qq/data is / . $o->data; my $foo = $o->data =~ /blah/ ? 'blah' : 'blah'; my $bar = "blah/" . $o->data; my $baz = "blah/" . $o->data; my $file = "/path/to/" . $o->data; if ( !-r $file ) { warn qq/Couldn't read file $file/; } my $str = substr $o->data, 3; }
凄く単純なことなのですが、これをアクセサから取得したデータをレキシカル変数にコピーして使い回す方法にしてみます。
sub use_tmp { my $data = $o->data; # コピー warn qq/data is $data/; my $foo = $data =~ /blah/ ? 'blah' : 'blah'; my $bar = "blah/$data"; my $baz = "blah/$data"; my $file = "/path/to/$data"; if ( !-r $file ) { warn qq/Couldn't read file $file/; } my $str = substr $data, 3; }
この 2 つのサブルーチンのベンチマーク結果です。この例だと 1.5 倍の高速化ができました。
Rate no_tmp use_tmp no_tmp 15873/s -- -34% use_tmp 23923/s 51% --
このように、高度なことをしなくてもパフォーマンスチューニングが可能なポイントというのは結構あります。