Elementary, ...

Notes &

アクセサモジュールベンチ - Lvalue速っ

Perl のアクセサモジュールとしては、Class::Accessor::Fast が人気すね。

# これは例
package Hoge;
use base 'Class::Accessor::Fast';
__PACKAGE__->mk_accessors(qw( req ));

sub new {
    my $class = shift;
    bless {
        url => 'http://e8y.net/',
        req => HTTP::Request->new,
    }, $class;
}

こんな風な、ハッシュに bless するよくあるクラスの場合に、そのハッシュのキーと同じ名前でgetterを作成しておくと、属性にアクセスする場合($hoge->url)と属性のオブジェクトのメソッドを呼ぶ場合($hoge->req->header())に、ブレース{} が途中に入ってこないので見やすいし、それが値なのかメソッドなのかを隠蔽してしまえてよい感じ。Class::Accessor::Fastはそれを簡単に作成できる。

もちろんアクセサ生成みたいなクラスユーティリティー的なモジュールは CPAN に五万とあります。その中の一つ、Class::Data::Accessor がアップデート(v0.03)されて、mk_classaccessors (複数形)メソッドが追加されているのを昨日知りました。

今までは mk_classaccessor (単数)しかなかったので論外だったのですが、気になったのでこの機会に Class::Accessor::Fast と同等のことができる既存アクセサモジュールをしらべてみることに。

  • Class::Accessor
    アクセサファミリーの長。いろいろ機能が盛り込まれている。set()やget()などなど。
  • Class::Accessor::Fast
    Class::Accessorの機能の内、上の例で説明したことをする部分に最適化したタイプ。
  • Class::Data::Accessor
    Class::Accessor::Fastと似ているのだけど、クラス変数のデフォルト値をセットできる。これについては、うまく使うとよさそうだけど、UNIVERSAL的怖さもあるので注意。参考
  • Class::Accessor::Lvalue
    Class::AccessorのsetterがLvalue。$hoge->directory =~ s/W+//; とかもできちゃう。
  • Class::Accessor::Lvalue::Fast
    Class::Accessorに対するClass::Accessor::Fastと同じ
  • Class::Accessor::Assert
    Class::Accessor::Fastと同じように使えるが、属性名の前後に+とか=を付けて、set時のチェックができる。いらないけど。
  • Class::AutoAccess
    AUTOLOADを使って、mk_accessors() 宣言をいらなくしたバージョン。AUTOLOADなのでイマイチ
  • Class::AutoAccess::Deep
    kentaroさんのこのエントリ から誕生した、多段ハッシュを矢印で追いかけられるタイプ。実用性はおいておいて、こういうのはだれかがやってCPANにあげておいてほしいというのがあるので、マル。

ベンチマーク

結果発表! (コードはここにおいてあります)

Benchmark: timing 100000 iterations of Assert, Auto, CA, Data, Deep, Fast, Lvalue, LvalueFast, Normal...
    Assert:  9 wallclock secs ( 8.69 usr +  0.00 sys =  8.69 CPU) @ 11507.48/s (n=100000)
      Auto:  3 wallclock secs ( 2.80 usr +  0.00 sys =  2.80 CPU) @ 35714.29/s (n=100000)
        CA:  4 wallclock secs ( 3.29 usr +  0.00 sys =  3.29 CPU) @ 30395.14/s (n=100000)
      Data:  2 wallclock secs ( 2.60 usr +  0.00 sys =  2.60 CPU) @ 38461.54/s (n=100000)
      Deep:  8 wallclock secs ( 8.38 usr +  0.00 sys =  8.38 CPU) @ 11933.17/s (n=100000)
      Fast:  2 wallclock secs ( 2.30 usr +  0.00 sys =  2.30 CPU) @ 43478.26/s (n=100000)
    Lvalue:  8 wallclock secs ( 9.61 usr +  0.00 sys =  9.61 CPU) @ 10405.83/s (n=100000)
LvalueFast:  3 wallclock secs ( 2.21 usr +  0.00 sys =  2.21 CPU) @ 45248.87/s (n=100000)
    Normal:  1 wallclock secs ( 1.27 usr +  0.01 sys =  1.28 CPU) @ 78125.00/s (n=100000)

              Rate Lvalue Assert  Deep    CA  Auto  Data  Fast LvalueFast Normal
Lvalue     10406/s     --   -10%  -13%  -66%  -71%  -73%  -76%       -77%   -87%
Assert     11507/s    11%     --   -4%  -62%  -68%  -70%  -74%       -75%   -85%
Deep       11933/s    15%     4%    --  -61%  -67%  -69%  -73%       -74%   -85%
CA         30395/s   192%   164%  155%    --  -15%  -21%  -30%       -33%   -61%
Auto       35714/s   243%   210%  199%   18%    --   -7%  -18%       -21%   -54%
Data       38462/s   270%   234%  222%   27%    8%    --  -12%       -15%   -51%
Fast       43478/s   318%   278%  264%   43%   22%   13%    --        -4%   -44%
LvalueFast 45249/s   335%   293%  279%   49%   27%   18%    4%         --   -42%
Normal     78125/s   651%   579%  555%  157%  119%  103%   80%        73%     --

Class::Accessor::Lvalue がかなり遅いのは、Tieをしているからかな。いろいろなことをする Class::Accessor、Class::Accessor::Assert や AUTOLOADな奴らは思った通り遅めですね。

そもそも調べるきっかけになった Class::Data::Accessor は、まあまあ健闘。Class::Accessor::Fastは、Fastを名乗るだけあって速い。

これ調べて一番びっくりだったのは、Class::Accessor::Lvalue::Fast の意外な速さ。ていうか、速っ! Lvalueって付け足しな機能なので、AUTOLOAD並に遅いと思っていたのでおどろきです。

まとめ

  • 普段は Class::Accessor::Fast でいいや。
  • クラスデータのデフォルト <-> インスタンスデータと使い分けたロジックを組みたいときは Class::Data::Accessor
  • My Hacks では、Class::Accesor::Lvalue::Fast を使う! で、Want.pm や Lvalue にどういう落とし穴があるのか把握した上でどうするか考えよう(でも、インターフェースが変わっちゃうからなあ。Lvalue惜しいけどみんなで使うのには向いてないね。。)