Catalyst – BAD_PERL対策

CentOS5とかでPerl5.8.8が遅い問題.DBIx::Classを使おうとすると,わざわざ教えてくれます.
WARNING: DBIx::Class::StartupCheck: This version of Perl is likely to exhibit
extremely slow performance for certain critical operations.
Please consider recompiling Perl. For more information, see
https://bugzilla.redhat.com/show_bug.cgi?id=196836 and/or
http://lists.scsys.co.uk/pipermail/dbix-class/2007-October/005119.html.
You can suppress this message by setting DBIC_NO_WARN_BAD_PERL=1 in your
environment.
対策はこちらを見ながらほとんどそのまんま.
http://d.hatena.ne.jp/dayflower/20080108/1199771246

以下時間がないので全く厳密な話ではないけど,Catalystの普通っぽいページのロードでテストした例.
$ ab -c 200 -n 1000 http://foo.bar/baz
対策前:
Total transferred: 3107000 bytes
HTML transferred: 2649000 bytes
Requests per second: 27.61 [#/sec] (mean)
Time per request: 7244.004 [ms] (mean)
Time per request: 36.220 [ms] (mean, across all concurrent requests)
Transfer rate: 83.77 [Kbytes/sec] received
対策後:
Total transferred: 3107000 bytes
HTML transferred: 2649000 bytes
Requests per second: 46.45 [#/sec] (mean)
Time per request: 4306.155 [ms] (mean)
Time per request: 21.531 [ms] (mean, across all concurrent requests)
Transfer rate: 140.91 [Kbytes/sec] received

ClearSilver, TemplateTookit, TT + HashRefInflator の速度比較

ここで念のため,ClearSilver + Catalyst::View::ClearSilver のベンチマークを取っておきます.
比較するのは ClearSilver(CS)とTemplateToolkit(TT)の2つですが,ClearSilverでは DBIx::Class::ResultClass::HashRefInflator を使いますので(Catalyst::View::ClearSilverのDBIx::Class対応を参照),TTでもHashRefInflator を用いたものを比較対象に追加しました(TT + HRI).
データはPositLogで実際に利用されるデータを用います.PositLogではご存じのとおり1つの情報のまとまりがスプライト形式で表現され,そのスプライトが1ページ中に100個から最大200個くらい登場することになります.テンプレートで繰り返し処理させるにはもってこいのデータです.
テンプレートの主な部分は次のようになります.TTのみですが,ClearSilverでも同じことがほぼ同じ構文で書けるので省略します.
[% FOREACH item IN sprites %]”spr[% item.sprite_id %]”:{“created_time”:”[% item.created_time %]”, “author”:”[% item.author_name %]”}[% IF item.margin_pixel %], “margin_s”:{“elder”:”[% item.margin_elder %]”, “position”:”[% item.margin_position %]”, “pixel”:”[% item.margin_pixel %]”}[% END %][% UNLESS loop.last() %],[% END %][% END %]
[% FOREACH item IN sprites %]
[% rwidth = item.width – 2 %]
<div class=’sprite’ id=’spr[% item.sprite_id %]’ style=’left:[% item.x %]px; top:[% item.y %]px; width:[% item.width %]px; z-index:[% item.z_index %]; visibility:hidden’><div class=’region’ style=’width:[% rwidth %]px;’><div class=’contents’ style=’border-width:[% item.border_width %]px; border-style:[% item.border_style %]; border-color:#[% item.border_color %]; padding:[% item.padding %]px; [% IF item.color %]color:#[% item.color %];[% END %] [% IF item.background_color %]background-color:#[% item.background_color %];[% END %]’>[% item.contents %]</div><div class=’info’>[% IF item.display_tag %]<span class=’tag’>[% item.tags %]></span><br>[% END %][% IF item.display_author %]<span class=’author’ style=’display:block;’>[% item.author_name %]</span>[% END %][% IF item.display_createdtime %]<span class=’time’ style=’display:block;’>[% item.created_time %]</span>[% END %][% IF item.display_uri %]<span class=’uri’ style=’display:block;’><a href=”[% item.uri %]”>link</a></span>[% END %]</div></div><span class=’plugin’ style=’display:none;’>[% item.plugin %]</span>
</div>
[% END %]
はい,読みづらいですが,FOREACHとIFの化け物みたいなものです.
データ.これもおなじみのものですが,ハッシュ表現ではこんな感じ.
my $newsprite = {
sprite_id => 0,
contents => ‘PositLog(ポジログ)は,手づくり感あふれるWeb構築システムです.電子地図のようにズーム/スクロールできるページの上に,直接文字や絵を書き込んだり,写真や動画を貼り付けることができます.また,動的な情報取得プラグインとRSS配信機能,グループでの共有,permalinkやタグ/注釈を用いた情報整理手段をWeb標準の技術で提供します.’,
created_time => ‘2008-05-02 00:00:00’,
modified_time => ‘2008-05-02 00:00:00’,
author_name => ‘hidekaz’,
public_author => ”,
public_password => ”,
border_width => 1,
border_style => ‘solid’,
padding => 1,
border_color => ‘f0f0f0’,
background_color => ‘f8f8f8’,
color => ‘808080’,
x => 10,
‘y’ => 10,
width => 100,
height => 100,
z_index => 50000,
tags => ”,
margin_elder => ”,
margin_pixel => ”,
margin_position => ”,
plugin => ”,
display_created_time => 1,
display_author => 1,
display_uri => 1,
display_tag => 1,
};
こいつがDBに入ってます.
Catalyst内でのベンチマークのコードはこんな感じ.
[$c->foo->search($bar)->all]はスプライトのrow objectの配列を,
[$c->foo->search_hashref($bar)->all]はHashRefInflatorを用いてスプライトのhash-refの配列を返すものと考えてください.

cmpthese(100,
{'CS' => sub{
my $sprites = [$c->foo->search_hashref($bar)->all];
$c->stash->{template} = "bench.cs";
$c->stash->{sprites} = $sprites;
$c->view('CS')->process($c);
},
'TT' => sub{
my $sprites = [$c->foo->search($bar)->all];
$c->stash->{template} = "bench.tt";
$c->stash->{sprites} = $sprites;
$c->view('TT')->process($c);
},
'TT + HRI' => sub{
my $sprites = [$c->foo->search_hashref($bar)->all];
$c->stash->{template} = "bench.tt";
$c->stash->{sprites} = $sprites;
$c->view('TT')->process($c);
},
});

スプライトデータが100件の場合はこちら.

Rate TT TT + HRI CS
TT 15.8/s -19% -32%
TT + HRI 19.7/s 24% -16%
CS 23.4/s 48% 19%

500件くらいまで増やしても結果は変わらず.
DB見に行くところが律速段階なので,ベンチから追い出すとこんな感じ.

Rate TT + HRI CS
TT + HRI 33.3/s -33%
CS 49.5/s 49%

はい,また差が開きました.
まぁ,こちらで紹介されてるように3800%とまではいきませんが,私がいま想定している一番重い処理で50%速くなるんだったら私としてはこのままClearSilverを使い続けても良さそうです.
ただ,TT + HRI でもそこそこ速いので,ときどきTTで複雑なことしたい,というような場合にはそちらのほうが良いかもしれません.
TTとの差が縮まってるのは,$hdf->setValue(STRING:$hdfpath,STRING:new_value)を繰り返し呼んで,大きなハッシュをHDFに変換するときのコストかな?
あと,gettext を使った場合のことや,HTML::Templateとの比較も気になってるけど,また時間に余裕ができたら,ということで.

DispatchType

はてなさんみたいにFQDN/直下にユーザ名が来るようなURI構成だと,自分でDispatchType書くのがスマートか.
DispatchTypeのソースを読んでたら,Catalyst::DispatchType::Chainedというのがあることに気づいた.これか.