馬鹿にできないアクセサのオーバーヘッド

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%      --

このように、高度なことをしなくてもパフォーマンスチューニングが可能なポイントというのは結構あります。