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というのがあることに気づいた.これか.

Catalyst-Authentication-Store-FromSub-Hash

Catalyst::Authentication::Store::DBIx::Class はModelをCatalystで管理していることが前提.
ModelがCatalystの管理外である場合は,Catalyst-Authentication-Store-FromSub-Hash を使って外部のModelにアクセスする.
つうわけで認証まわりはOK.これで後顧の憂いはすべて絶てたか.
おやすみなさい.

Catalyst – M

行き詰まってJiftyの導入というでっかい現実逃避をしている間に名案を思いついたので実行.
CatalystではModelを管理しないことにした.独自にDBIx::Classを利用してModelをつくるほうが僕にとっては都合がよいのでした.
こんな時間になったけど,ああ,すっきり.
FormValidatorまわりも少し書き直す必要があるので,それやってから寝よう.
できた.C,R まできたので,あとはU,D,認証でだいたい一揃い.
おやすみなさい.

ClearSilver + Catalyst::Plugin::FormValidator::Simple(2)

前回の手間を省力化.
form_error.yml の内容を動的に生成.

# myapp.yml
validator:
plugins:
- Japanese
options:
charset: utf8

ユーザ作成フォームのテンプレート.

<html>
<head>
<title><?cs var:page.title ?></title>
<head>
<body>
<h1>ユーザ作成</h1>
<?cs if:subcount(page.status_msg) > 0 ?>
<div id="statusmsg" style="color:red">
<?cs each:item = page.status_msg ?>
<?cs if:item == "id_not_blank" ?>ユーザIDが空白です.<br><?cs /if ?>
<?cs if:item == "id_length" ?>ユーザIDの長さは,4字以上20字以下です.<br><?cs /if ?>
<?cs if:item == "id_regex" ?>ユーザIDに利用できな文字が含まれています.<br><?cs /if ?>
<?cs if:item == "email_not_blank" ?>E-mailが空白です.<br><?cs /if ?>
<?cs if:item == "email_length" ?>E-mailの長さは,40字以内です.<br><?cs /if ?>
<?cs if:item == "email_email_loose" ?>E-mailの形式が正しくありません.<br><?cs /if ?>
<?cs if:item == "password_not_blank" ?>パスワードが空白です.<br><?cs /if ?>
<?cs if:item == "password_length" ?>パスワードの長さは,8字以上20字以下です.<br><?cs /if ?>
<?cs if:item == "password_regex" ?>パスワードに利用できない文字が含まれています.<br><?cs /if ?>
<?cs /each ?>
</div>
<?cs /if ?>
(中略.id, email, password を入力するフォーム.)
</body>
</html>

コントローラ.ユーザ作成フォーム.
前回と同じ.

sub create : Local {
my ( $self, $c ) = @_;
 $c->stash->{page} = {
title => "Create User",
status_msg => $c->flash->{status_msg},
 };
 $c->view('CS')->process($c);
}

ユーザ作成フォームから呼び出されるアクション

sub create_do : Local {
my ( $self, $c ) = @_;
my $validate_rule = [
id => [qw/NOT_BLANK/, [qw/LENGTH 4 20/], ['REGEX', qr/^[\w\-_]+$/]],
email => [qw/NOT_BLANK EMAIL_LOOSE/, [qw/LENGTH 1 40/]],
password => [qw/NOT_BLANK/, [qw/LENGTH 8 20/], ['REGEX', qr/^[a-zA-Z0-9\^\~\_\!\#\%\&\(\)\*\+\-\/\.\:\;\\'\"\\\?\@\[\]\^\`\{\|\}]/]+$]
];
$c->form($validate_rule);
if($c->form->has_error){
# messagesの自動生成
my $message_table = {};
while(@$validate_rule){
my $key = shift @$validate_rule;
my $value = shift @$validate_rule;
foreach my $rule (@$value){
if(ref($rule) eq 'ARRAY'){
$rule = (@$rule)[0];
}
$message_table->{DEFAULT}{$key}{$rule} = $key . "_" . lc($rule);
}
}
FormValidator::Simple->set_messages($message_table);
my $messages =$c->form->messages("");
$c->flash->{status_msg} = $messages;
$c->response->redirect($c->uri_for('create'));
}
(以下略)
}

$message_tableの中身はこうなります.

{
'DEFAULT' => {
'id' => {
'LENGTH' => 'id_length',
'NOT_BLANK' => 'id_not_blank',
'REGEX' => 'id_regex'
}
'email' => {
'LENGTH' => 'email_length',
'NOT_BLANK' => 'email_not_blank',
'EMAIL_LOOSE' => 'email_email_loose'
},
'password' => {
'LENGTH' => 'password_length',
'NOT_BLANK' => 'password_not_blank',
'REGEX' => 'password_regex'
},
}
};

FormValidatorによるチェック結果を配列に収めるには
my $messages =$c->form->messages(“”);
(引数が”なのでDEFAULTが利用される.)
チェックに引っかかった項目は,DEFAULT内の対応する値が配列に入ります.$messagesは配列へのリファレンス.
最終的に$c->stash->{page}の中身はこんな感じになります.

$c->stash->{page} =  {
'title' => 'Create User',
'status_msg' => [
'id_regex',
'email_not_blank',
'password_not_blank'
]
};

これは Catalyst::View::ClearSilver 内で次のようなHDFに変換されます.

page {
title = Create User
status_msg {
0 = id_regex
1 = email_not_blank
2 = password_not_blank
}
}