Class::Accessor::Faster というのが出来てた

Class::Accessor シリーズに 0.27 で Class::Accessor::Faster なるものが追加されていました(Fast と同様 Class::Accessor を普通に入れると一緒に入ります)。

This is a faster but less expandable version of Class::Accessor::Fast.

C::A::Fast 「よりも」速いんですか?まじっすか。ということでベンチマークをとってみました。

幸いにも?トミーさんがhttp://e8y.net/blog/2006/05/26/p120.htmlベンチマークしてらっしゃったので,スクリプトインスパイアーコピーしました。つっても今回は他のアクセサに興味がなかったので,Class::Accessor 三兄弟と通常ハッシュ版,意外に速い Lvalue::Fast を含む Class::Accessor::Lvalue 兄弟の6者で戦わせてみました。

CA/Fast.pm を CA/Faster.pm にコピーして Class::Accessor::Faster を使うように変更。これでベンチマークをとろうとしたところ,ごちゃごちゃ怒られます。どうも C::A::Faster はきちんと new を呼んでやらなくてはいけない模様(トミーさんの元ソースだと空ハッシュに bless してるだけでした)。

package CA::Faster;
use strict;
use base 'Class::Accessor::Faster';
__PACKAGE__->mk_accessors(qw(name conf say));

sub new { shift->SUPER::new(@_); }

1;

のように親の new を呼んでやるように変更。

さて,注目の実行結果は?

Benchmark: timing 100000 iterations of CA, Fast, Faster, Lvalue, LvalueFast, Normal...
        CA: 13 wallclock secs (13.63 usr +  0.01 sys = 13.64 CPU) @ 7331.38/s (n=100000)
      Fast:  9 wallclock secs ( 9.75 usr +  0.00 sys =  9.75 CPU) @ 10256.41/s (n=100000)
    Faster: 14 wallclock secs (13.89 usr +  0.01 sys = 13.90 CPU) @ 7194.24/s (n=100000)
    Lvalue: 41 wallclock secs (40.95 usr +  0.01 sys = 40.96 CPU) @ 2441.41/s (n=100000)
LvalueFast:  9 wallclock secs ( 8.81 usr +  0.01 sys =  8.82 CPU) @ 11337.87/s (n=100000)
    Normal:  5 wallclock secs ( 5.32 usr +  0.00 sys =  5.32 CPU) @ 18796.99/s (n=100000)

Class::Accessor::Faster めちゃ遅っ!通常版の Class::Accessor より遅いじゃないですか。

さて,何故でしょう。


おわかりだとは思いますが,上で書いたように元スクリプトでは空ハッシュリファレンスにパッケージを bless してるだけなのに対して,Faster では SUPER::new をディスパッチしています。このせいではないかと。

ということで,テストスクリプトを,

... (前略) ...

my $c_n  = CA::Normal->new;
my $c_c  = CA->new;
my $c_f  = CA::Fast->new;
my $c_fr = CA::Faster->new;
my $c_l  = CA::Lvalue->new;
my $c_lf = CA::LvalueFast->new;

... (後略) ...

のようにあらかじめオブジェクトを生成しておいて,テスト関数の中ではそれを参照するように変更しました*1

さてさて,本当の結果は…

Benchmark: timing 100000 iterations of CA, Fast, Faster, Lvalue, LvalueFast, Normal...
        CA: 12 wallclock secs (12.03 usr +  0.01 sys = 12.04 CPU) @ 8305.65/s (n=100000)
      Fast:  8 wallclock secs ( 8.16 usr +  0.01 sys =  8.17 CPU) @ 12239.90/s (n=100000)
    Faster:  7 wallclock secs ( 8.04 usr +  0.00 sys =  8.04 CPU) @ 12437.81/s (n=100000)
    Lvalue: 40 wallclock secs (39.11 usr +  0.01 sys = 39.12 CPU) @ 2556.24/s (n=100000)
LvalueFast:  8 wallclock secs ( 7.41 usr +  0.00 sys =  7.41 CPU) @ 13495.28/s (n=100000)
    Normal:  4 wallclock secs ( 3.94 usr +  0.00 sys =  3.94 CPU) @ 25380.71/s (n=100000)

              Rate    Lvalue         CA      Fast    Faster LvalueFast    Normal
Lvalue      2556/s        --       -69%      -79%      -79%       -81%      -90%
CA          8306/s      225%         --      -32%      -33%       -38%      -67%
Fast       12240/s      379%        47%        --       -2%        -9%      -52%
Faster     12438/s      387%        50%        2%        --        -8%      -51%
LvalueFast 13495/s      428%        62%       10%        9%         --      -47%
Normal     25381/s      893%       206%      107%      104%        88%        --

無事「アクセスする部分に関しては」Fast より速くなりました。

結論

Class::Accessor::Fast(や Class::Accessor)は,わざわざ new を呼ぶ必要ないんですよね。実際 SUPER::new していない継承モジュールも結構ある気がします。Faster はオブジェクトの生成時に時間をとられるので,アクセサ部分でこれくらいの違いしかでないのであれば別段 Faster 使うまでもないかなぁと思いました。

*1:公正を期すためにも他のモジュールでも new するように変えたほうがよかったかも