Catalystのオーバーヘッド~Catalyst::Dispatcher::dispatch編

|
続いてCatalyst::Dispatcher::dispatchのオーバーヘッドについて。 dispatchではCatalystの面白さでもあり,複雑さにもつながるURL⇔クラス・メソッドのマッピングを処理しているので,それなりのオーバーヘッドは仕方ないだろう。 C::D::dispatchではsetupでのハンドラ登録を含めて考慮しないとならない。 Tociyuki::Diary Catalyst解体が参考になる。 $ENV{CATALYST_DISPATCHER}とかでDispatcherの切り替えが可能なことを知らなかった。ふむふむ。 最初のDProfの結果とCatalystのオーバーヘッド~Catalyst::Engine::FastCGIのprepare系でいじった現時点でのab2の結果をおさらいしておこう。 %Time ExclSec CumulS #Calls sec/call Csec/c  Name  27.5  0.350  4.270  10002  0.0000 0.0004  Catalyst::Dispatcher::dispatch  9.30  0.540  1.440  50009  0.0000 0.0000  Tree::Simple::Visitor::FindByPath::new  5.81  0.540  0.900  50009  0.0000 0.0000  Tree::Simple::Visitor::FindByPath::_init   この辺が重いところ。 Tree::Simple::Visitorなんてなんで? と思ったらTree::SimpleによってURLツリーの木構造を保持していた訳で。 Concurrency Level:      1 Time taken for tests:   13.996904 seconds Complete requests:      10000 Failed requests:        0 Write errors:           0 Keep-Alive requests:    9412 Total transferred:      1717060 bytes HTML transferred:       480000 bytes Requests per second:    714.44 [#/sec] (mean) Time per request:       1.400 [ms] (mean) Time per request:       1.400 [ms] (mean, across all concurrent requests) Transfer rate:          119.74 [Kbytes/sec] received   714.44 req/s。前に載せたものと値は違うが,さすがにこの辺からゆらぎが大きくなってきた。 amavisd-newとかまで入ってるサーバ上でベンチしてるからゆらぎが大きいなぁ。 もうちょっと落ち着いた環境が欲しくなってしまう。 さて。C::D::dispatchはこんなことをしている。 sub dispatch {     my $c         = shift;     my $action    = $c->req->action;     my $namespace = '';     $namespace = ( join( '/', @{ $c->req->args } ) || '/' )       if $action eq 'default';     unless ($namespace) {         if ( my $result = $c->get_action($action) ) {             $namespace = Catalyst::Utils::class2prefix( $result->[0]->[0]->[0],                 $c->config->{case_sensitive} );         }     }     my $default = $action eq 'default' ? $namespace : undef;     my $results = $c->get_action( $action, $default, $default ? 1 : 0 );     $namespace ||= '/';     if ( @{$results} ) {         # Execute last begin         $c->state(1);         if ( my $begin = @{ $c->get_action( 'begin', $namespace, 1 ) }[-1] ) {             $c->execute( @{ $begin->[0] } );             return if scalar @{ $c->error };         }         # Execute the auto chain         my $autorun = 0;         for my $auto ( @{ $c->get_action( 'auto', $namespace, 1 ) } ) {             $autorun++;             $c->execute( @{ $auto->[0] } );             return if scalar @{ $c->error };             last unless $c->state;         }         # Execute the action or last default         my $mkay = $autorun ? $c->state ? 1 : 0 : 1;         if ( ( my $action = $c->req->action ) && $mkay ) {             if ( my $result = @{ $c->get_action( $action, $default, 1 ) }[-1] )             {                 $c->execute( @{ $result->[0] } );             }         }         # Execute last end         if ( my $end = @{ $c->get_action( 'end', $namespace, 1 ) }[-1] ) {             $c->execute( @{ $end->[0] } );             return if scalar @{ $c->error };         }     }     else {         my $path  = $c->req->path;         my $error = $path           ? qq/Unknown resource "$path"/           : "No default action defined";         $c->log->error($error) if $c->debug;         $c->error($error);     } }   何度かget_actionしまくって,必要なハンドラを探す。begin, auto, endなども含まれるのでget_actionが何度か呼ばれている訳だ。今回は各リクエストごとに5回。 C::D::get_actionの中身が以下。 sub get_action {     my ( $c, $action, $namespace, $inherit ) = @_;     return [] unless $action;     $namespace ||= '';     $inherit   ||= 0;     if ($namespace) {         $namespace = '' if $namespace eq '/';         my $parent = $c->tree;         my @results;         if ($inherit) {             my $result = $c->actions->{private}->{ $parent->getUID }->{$action};             push @results, [$result] if $result;             my $visitor = Tree::Simple::Visitor::FindByPath->new;             for my $part ( split '/', $namespace ) {                 $visitor->setSearchPath($part);                 $parent->accept($visitor);                 my $child = $visitor->getResult;                 my $uid   = $child->getUID if $child;                 my $match = $c->actions->{private}->{$uid}->{$action} if $uid;                 push @results, [$match] if $match;                 $parent = $child if $child;             }         }         else {             if ($namespace) {                 my $visitor = Tree::Simple::Visitor::FindByPath->new;                 $visitor->setSearchPath( split '/', $namespace );                 $parent->accept($visitor);                 my $child = $visitor->getResult;                 my $uid   = $child->getUID if $child;                 my $match = $c->actions->{private}->{$uid}->{$action}                   if $uid;                 push @results, [$match] if $match;             }             else {                 my $result =                   $c->actions->{private}->{ $parent->getUID }->{$action};                 push @results, [$result] if $result;             }         }         return \@results;     }     elsif ( my $p = $c->actions->{plain}->{$action} ) { return [ [$p] ] }     elsif ( my $r = $c->actions->{regex}->{$action} ) { return [ [$r] ] }     else {         for my $i ( 0 .. $#{ $c->actions->{compiled} } ) {             my $name  = $c->actions->{compiled}->[$i]->[0];             my $regex = $c->actions->{compiled}->[$i]->[1];             if ( my @snippets = ( $action =~ $regex ) ) {                 return [ [ $c->actions->{regex}->{$name}, $name, \@snippets ] ];             }         }     }     return []; }   この中でTree::Simple::Visitorのインスタンスを生成しているのだが,実はそれにはコストがかかっているよ,という訳だ。(DProf参照。1.44秒/10000req) しかし,VisitorパターンできちんとsetSearchPath()ってしているのに毎回オブジェクト生成する必要無いだろ? というのがこのエントリのテーマ。 __PACKAGE__->mk_classdataでvisitorを追加してここにインスタンス保存しちゃえ,と。同じvisitorを再利用しましょうね,と。 やってみた。変更したソースは省略。 Concurrency Level:      1 Time taken for tests:   12.707316 seconds Complete requests:      10000 Failed requests:        0 Write errors:           0 Keep-Alive requests:    9412 Total transferred:      1717060 bytes HTML transferred:       480000 bytes Requests per second:    786.95 [#/sec] (mean) Time per request:       1.271 [ms] (mean) Time per request:       1.271 [ms] (mean, across all concurrent requests) Transfer rate:          131.89 [Kbytes/sec] received   ほら,70req/sくらい速くなった。 後は,ツリーの探索が効率悪くない? ってのもあるがそこまで大改造はしないことにしておく。 begin/auto/default/end,個別のnameのそれぞれについて毎回パスを探索しているような。 あとは多用している正規表現でm//oしても良いんじゃない? ってくらいかな。 finalizeで重いのはfinalize_headersで$c->res->headersを文字列化するところ。 あんまりいじりようが無いか。libwww-perlのHTTP::Headersを使ってる限りは改善しようがない。

Trackback URL for this post:

http://www.typemiss.net/trackback/9