%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
