最近Gitの共有リポジトリを構築する機会があったのですが、以下のような感じで運用しています。
- gitプロトコルではなくsshでアクセス(ポート開けるのだるいので)
- サーバ側にgitユーザを作って、アクセスさせたい人にsshの公開鍵をもらってそれを /home/git/.ssh/authorized_keys に追加して公開鍵認証
- git clone ssh://git@lampetty.net/var/cache/git/hoge.git みたいな感じでclone
いちいちサーバ側にユーザ追加して、gitリポジトリのディレクトリにパーミッション付与するよりこっちの方がたぶん楽。
Perlで日付関連の処理をする代表的なモジュールにDateTimeというものがありますが、メモリ消費量が激しいのがずっと気になっていました。でで、Time::Pieceが5.10.1からPerlに標準添付になったという話を聞いて、乗り換えようかどうか検討しています。Perlメモ/Time::Pieceモジュール – Walrus, Digit.を見ると、DateTimeでできることは大体できるので、以下のユースケースでの速度面を測ってみます。
環境は以下で、ベンチマークのスクリプトは最後に載せてあります。
- OS: Ubuntu 9.04 amd64
- Perl: 5.10.0 x86_64-linux-gnu-thread-multi
- DateTime 0.51
- Time::Piece 1.15
use 時のメモリ使用量
こちらのgtop.plを使って測ります。
$ gtop.pl 'use DateTime'
10.2M : use DateTime
$ gtop.pl 'use Time::Piece'
2.5M : use Time::Piece
おおお、なんと4分の1!
現在日時でオブジェクトを生成する
$ ./benchmark_datetime.pl now
Benchmark: running now_datetime, now_time_piece for at least 3 CPU seconds...
now_datetime: 4 wallclock secs ( 3.03 usr + 0.00 sys = 3.03 CPU) @ 3149.83/s (n=9544)
now_time_piece: 2 wallclock secs ( 2.58 usr + 0.53 sys = 3.11 CPU) @ 52694.86/s (n=163881)
Rate now_datetime now_time_piece
now_datetime 3150/s -- -94%
now_time_piece 52695/s 1573% --
15倍!
日付オブジェクトに対する日付の加算
$ ./benchmark_datetime.pl add
Benchmark: running add_datetime, add_time_piece for at least 3 CPU seconds...
add_datetime: 3 wallclock secs ( 3.05 usr + 0.00 sys = 3.05 CPU) @ 1853.44/s (n=5653)
add_time_piece: 3 wallclock secs ( 2.76 usr + 0.42 sys = 3.18 CPU) @ 54969.81/s (n=174804)
Rate add_datetime add_time_piece
add_datetime 1853/s -- -97%
add_time_piece 54970/s 2866% --
Time::Pieceの圧倒的勝利。28倍!
日付オブジェクト同士の減算
$ ./benchmark_datetime.pl subtract
Benchmark: running subtract_datetime, subtract_time_piece for at least 3 CPU seconds...
subtract_datetime: 3 wallclock secs ( 3.00 usr + 0.00 sys = 3.00 CPU) @ 4065.00/s (n=12195)
subtract_time_piece: 4 wallclock secs ( 3.15 usr + 0.00 sys = 3.15 CPU) @ 132329.84/s (n=416839)
Rate subtract_datetime subtract_time_piece
subtract_datetime 4065/s -- -97%
subtract_time_piece 132330/s 3155% --
引き算もTime::Pieceの方が圧倒的に速いですね。
日付オブジェクト同士の比較
$ ./benchmark_datetime.pl compare
Benchmark: running compare_datetime, compare_time_piece for at least 3 CPU seconds...
compare_datetime: 3 wallclock secs ( 3.04 usr + 0.00 sys = 3.04 CPU) @ 47709.21/s (n=145036)
compare_time_piece: 4 wallclock secs ( 3.09 usr + -0.01 sys = 3.08 CPU) @ 173623.70/s (n=534761)
Rate compare_datetime compare_time_piece
compare_datetime 47709/s -- -73%
compare_time_piece 173624/s 264% --
日付オブジェクトを文字列にする
$ ./benchmark_datetime.pl stringify
Benchmark: running stringify_datetime, stringify_time_piece for at least 3 CPU seconds...
stringify_datetime: 3 wallclock secs ( 3.00 usr + 0.00 sys = 3.00 CPU) @ 44470.33/s (n=133411)
stringify_time_piece: 3 wallclock secs ( 2.45 usr + 0.66 sys = 3.11 CPU) @ 116681.35/s (n=362879)
Rate stringify_datetime stringify_time_piece
stringify_datetime 44470/s -- -62%
stringify_time_piece 116681/s 162% --
まとめ
総じてDateTimeよりTime::Pieceの方が高い性能を叩き出しました。速度にシビアな状況ではTime::Pieceを使った方が良いと感じました。こういう罠もあるみたいなので気をつけなくてはいけないところもありますが… インタフェースはどちらも綺麗に出来ているので使い勝手としては同じぐらいかなと思います。それにしてももっと速くTime::Pieceを検証しておけばよかったと思う今日この頃です。
ベンチマークスクリプト(benchmark_datetime.pl)
#!/usr/bin/env perl
use strict;
use warnings;
use Benchmark qw(cmpthese timethese);
use DateTime;
use Time::Piece;
use Time::Seconds;
my $timezone = DateTime::TimeZone->new(name => 'local');
#----------------------#
# now
#----------------------#
sub now_datetime {
my $now = DateTime->now(time_zone => $timezone);
}
sub now_time_piece {
my $now = localtime;
}
#----------------------#
# add
#----------------------#
my $add_dt = DateTime->now(time_zone => $timezone);
sub add_datetime {
$add_dt->add(days => 1);
}
my $add_tp = localtime;
sub add_time_piece {
$add_tp += ONE_DAY;
}
#----------------------#
# subtract
#----------------------#
my $sub_dt1 = DateTime->now(time_zone => $timezone);
my $sub_dt2 = DateTime->new(
year => 2008,
month => 12,
day => 1,
);
sub subtract_datetime {
my $dur = $sub_dt1->subtract_datetime($sub_dt2);
}
my $sub_tp1 = localtime;
my $sub_tp2 = Time::Piece->strptime('2008-12-01', '%Y-%m-%d');
sub subtract_time_piece {
my $sec = $sub_tp1 - $sub_tp2;
}
#----------------------#
# compare
#----------------------#
my $compare_dt1 = DateTime->now(time_zone => $timezone);
my $compare_dt2 = DateTime->now(time_zone => $timezone);
sub compare_datetime {
my $result = DateTime->compare($compare_dt1, $compare_dt2) <= 0;
}
my $compare_tp1 = localtime;
my $compare_tp2 = localtime;
sub compare_time_piece {
my $result = $compare_tp1 <= $compare_tp2;
}
#----------------------#
# stringify
#----------------------#
my $now_dt = DateTime->now(time_zone => $timezone);
sub stringify_datetime {
$now_dt->strftime("%Y-%m-%d");
}
my $now_tp = localtime;
sub stringify_time_piece {
$now_tp->strftime("%Y-%m-%d");
}
#----------------------#
# main
#----------------------#
my $mode = shift @ARGV || 'now';
my $count = shift @ARGV || -3;
if ($mode eq 'now') {
cmpthese timethese $count, {
'now_datetime' => \&now_datetime,
'now_time_piece' => \&now_time_piece,
};
} elsif ($mode eq 'add') {
cmpthese timethese $count, {
'add_datetime' => \&add_datetime,
'add_time_piece' => \&add_time_piece,
};
} elsif ($mode eq 'subtract') {
cmpthese timethese $count, {
'subtract_datetime' => \&subtract_datetime,
'subtract_time_piece' => \&subtract_time_piece,
};
} elsif ($mode eq 'compare') {
cmpthese timethese $count, {
'compare_datetime' => \&compare_datetime,
'compare_time_piece' => \&compare_time_piece,
};
} elsif ($mode eq 'stringify') {
cmpthese timethese $count, {
'stringify_datetime' => \&stringify_datetime,
'stringify_time_piece' => \&stringify_time_piece,
};
}
Now Text::MicroTemplate is even faster than HTML::Template::Pro – use GFx::WebLog;
Text::MicroTemplate 0.10で動作速度が改善されたとのことなので、自分のところでもベンチマークしてみました。
$ perl benchmark_templates.pl 1
Perl/5.10.0 (x86_64-linux-gnu-thread-multi)
HTML::Template/2.9
HTML::Template::Compiled/0.94
HTML::Template::Pro/0.92
Template/2.20
Text::MicroTemplate/0.10
Benchmark: running HT, HT::C, HT::Pro, MT, TT for at least 1 CPU seconds...
HT: 1 wallclock secs ( 1.03 usr + 0.00 sys = 1.03 CPU) @ 1094.17/s (n=1127)
HT::C: 1 wallclock secs ( 1.03 usr + 0.00 sys = 1.03 CPU) @ 11598.06/s (n=11946)
HT::Pro: 2 wallclock secs ( 0.82 usr + 0.29 sys = 1.11 CPU) @ 17611.71/s (n=19549)
MT: 1 wallclock secs ( 0.96 usr + 0.08 sys = 1.04 CPU) @ 15904.81/s (n=16541)
TT: 1 wallclock secs ( 1.08 usr + 0.00 sys = 1.08 CPU) @ 7110.19/s (n=7679)
Rate HT TT HT::C MT HT::Pro
HT 1094/s -- -85% -91% -93% -94%
TT 7110/s 550% -- -39% -55% -60%
HT::C 11598/s 960% 63% -- -27% -34%
MT 15905/s 1354% 124% 37% -- -10%
HT::Pro 17612/s 1510% 148% 52% 11% --
何回かやったのですが、自分の環境ではまだHTML::Template::Proが速いみたい。でも僅差なのでまったく気にならないレベルですね。とにかくgfxさん++すぎる。
PerlでWebアプリを作る時のテンプレートエンジンをどれにしようか相変わらず模索中なのですが、以下の2つのエントリーで動作速度とメモリについて調べた結果、どうやらText::MicroTemplateとHTML::Template::Proのどちらかを選択するのが良いだろうと思っています。
Text::MicroTemplateの良い点
- Perlのコードが書けるので自由度が高い
- そこそこ速い
- 省メモリ
- デフォルトでHTMLエスケープしてくれるのでセキュリティ的にもグッド
- コードがシンプルなので拡張しやすい
- loopやifで改行の制御が出来る( 〜 ?>と ? )
HTML::Template::Proの良い点
- 文法がシンプル
- 爆速
- そこそこ省メモリ
- register_functionでテンプレート内から呼び出せる関数が登録できるので、拡張性はそれなりにある
Text::MicroTemplateのイマイチな点
- 自由度が高い分、色々できてしまう(Viewにロジックをガリガリ書いたりとか)
- Includeがデフォルトでは出来ない(Text::MicroTemplate::Extended使えば似たようなことはできる)
HTML::Template::Proのイマイチな点
- <TMPL_IF>、<TMPL_LOOP>などの制御を書く時の改行のコントロールが難しい
- デフォルトではHTMLエスケープがかからないので、自前で自動エスケープのロジックを書くかテンプレート側で毎度エスケープしなくてはいけない
「<TMPL_IF>、<TMPL_LOOP>で改行のコントロールが難しい」というのは、例えば以下のテンプレートファイルがあって、それを実際に出力した時のHTMLに改行が入ってしまう、ということです。
テンプレートファイル
<html>
<head></head>
<body>
<TMPL_IF NAME=hoge>
hoge
</TMPL_IF>
</body>
</html>
出力されるHTML
<html>
<head></head>
<body>
#ここに改行
hoge
#ここにも改行
</body>
</html>
出力するのがHTMLであればそんなに問題はないのですが、メールの文面をテンプレートエンジンで動的に生成したい場合、改行が実直に反映されてしまうので、いつもどうしようか悩んでしまいます。この改行のためにHTMLの生成とメールの生成で別々のテンプレートエンジン使うのも微妙ですし。
もしかするとHTML::Template::Proの filter 機能でなんとかできるのかもしれませんが、まだそこまでは考えられてないです。というわけで相変わらずテンプレートエンジンどうしようか悩み中…
Text::MicroTemplateの速度を簡単にベンチマークでText::MicroTemplateの動作速度を簡単にベンチマークしましたが、今回はメモリ使用量を他のテンプレートエンジンと比較してみました。環境はUbuntu 9.04 amd64, Perl 5.10.0 で、GTopを使って測っています。
useした時のメモリ使用量
独自に作ったスクリプト(use_memory_size.pl)でuse時のメモリ使用量を測ってみます。
$ ./use_memory_size.pl
3.2M : use HTML::Template
11.7M : use HTML::Template::Compiled
2.5M : use HTML::Template::Pro
264k : use Text::MicroTemplate
3.6M : use Template
HTML::Template::Compiledは多くのモジュールで構成されているので、メモリ使用量が多そうな感じです。一方、ソースコードがとてもコンパクトな Text::MicroTemplate は他のテンプレートエンジンと比べてメモリ使用量が一桁少ない。これはすごい。
use_memory_size.pl
#!/usr/bin/env perl
use strict;
use warnings;
for my $module (qw(HTML::Template
HTML::Template::Compiled
HTML::Template::Pro
Text::MicroTemplate
Template)) {
system "gtop.pl 'use $module'";
}
(プロセスのメモリ使用量を調べるために必要なGTopモジュール(およびlibgtop2)をインストールして、下記スクリプトをgtop.plとしてどこかパスの通ってるところに保存しておいてください)
gtop.pl
#!/usr/bin/env perl
use strict;
use warnings;
use GTop;
unless (@ARGV) {
print STDERR "Argument required.\n";
exit 1;
}
my $gtop = GTop->new;
my $before = $gtop->proc_mem($$)->size;
eval $ARGV[0];
die $@ if $@;
my $after = $gtop->proc_mem($$)->size;
my $diff = GTop::size_string($after - $before);
print "$diff : $ARGV[0]\n";
newしてレンダリングさせた後のメモリ使用量
モジュールをuseした時のメモリ使用量だけでなく、newしてoutputした後のメモリ使用量も調べてみました。結果は以下になります。
$ ./new_memory_size.pl
3.2M : use HTML::Template;my $t = HTML::Template->new(filename => q{template_speed_file.ht});my $s = $t->output;
11.7M : use HTML::Template::Compiled;my $t = HTML::Template::Compiled->new(filename => q{template_speed_file.ht});my $s = $t->output;
2.5M : use HTML::Template::Pro;my $t = HTML::Template::Pro->new(filename => q{template_speed_file.ht});my $s = $t->output;
796k : use Text::MicroTemplate::File;my $t = Text::MicroTemplate::File->new;my $s = $t->render_file(q{template_speed_file.mt})->as_string;;
11.4M : use Template;my $t = Template->new;$t->process(q{template_speed_file.tt}, {}, \my $s);
相変わらずText::MicroTemplateは良好な成績ですね。あまりメモリを積んでいないマシンでもこれなら安心して使えそうな感じです。HTML::Template::Proも2.5MBと省メモリですね。スピードも爆速なので、テンプレートにゴリゴリPerlのコードを書く必要がない場合はこれが一番よいかもしれません。
new_memory_size.pl
#!/usr/bin/env perl
use strict;
use warnings;
# HTML::Template
my $ht = <<'...';
use HTML::Template;
my $t = HTML::Template->new(filename => q{template_speed_file.ht});
my $s = $t->output;
...
# HTML::Template::Compiled
my $htc = <<'...';
use HTML::Template::Compiled;
my $t = HTML::Template::Compiled->new(filename => q{template_speed_file.ht});
my $s = $t->output;
...
# HTML::Template::Pro
my $htp = <<'...';
use HTML::Template::Pro;
my $t = HTML::Template::Pro->new(filename => q{template_speed_file.ht});
my $s = $t->output;
...
# Text::MicroTemplate
my $mt = <<'...';
use Text::MicroTemplate::File;
my $t = Text::MicroTemplate::File->new;
my $s = $t->render_file(q{template_speed_file.mt})->as_string;;
...
# Template
my $tt = <<'...';
use Template;
my $t = Template->new;
$t->process(q{template_speed_file.tt}, {}, \my $s);
...
for my $code ($ht, $htc, $htp, $mt, $tt) {
$code =~ s/\n//g;
system qq{gtop.pl '$code'};
}
インフラエンジニアじゃないけどインフラエンジニア勉強会 hbstudy#5に参加してきました。もともとこのイベントには参加したいなぁと思っていて、参加登録したらいいタイミングで松信さんが講演することにw 貴重なMySQLのチューニングの話が生で聞けてとてもよかったよかった。
あと、最初にPostgreSQLの話をしていた永安さんのセッションもよかった。普通DBの入門の話ってあまりつっこんだ運用の話は出てこないと思うのですが、運用を意識した入門編でこういうのはすごい貴重だったのではないかと。
PostgreSQL安定運用のコツ2009
永安さんの話はスライドを見てもらえばほぼ全てわかります。スライドが充実し過ぎていてあまりメモを取っていなかったのですが、最近のポスグレはAuto Vacuumなんて仕組みがあって、あまりVacuumを意識しなくてよいのだなぁと。あとポスグレもチューニングはいかに共有バッファをうまく使うかっていうところで、あんまりMySQLと変わらないんだなって思いました。
Linux MySQLサーバーのパフォーマンスチューニング
資料(PDF)
MySQLのチューニングの基本はデータサイズを小さくしていかにメモリにのっけるか、という話。たとえば、日時を格納するカラムはDATETIME(8byte使用)じゃなくてTIMESTAMP(4byte)を使えとか。statusみたいな1/0しか入らないカラムは文字列型じゃなくてTINYINTかENUM使えとか。ちなみに日時は2038年問題が気にならないのであれば、UNIXTIME化してINT型のフィールドにしてしまうという荒技もありますよね。アプリケーション側でいちいち変換しなくてはいけないですが。
あと「巨大なTEXT/BLOBはクエリ効率を悪化させる」という話で、巨大なデータを格納するカラムは別テーブルにすると、それ以外のカラムのデータをSELECTするときに悪影響が出ないらしい。ちょっとどういう話か失念してしまったので、資料が公開されたら復習します。一定以上の大きさのテキストフィールドを別領域に保存するストレージエンジンとして、Falcon, PBXTがあるとのこと。ちなみに、HDDが一秒間に処理できるランダムI/Oはせいぜい数百ぐらいなので、とても遅いですと。
あとは実データを引かずにCovering Index(インデックスだけを読む検索)でうまく処理する方法もあるそうで、
- テーブルのレコードにアクセスする必要がなくなるので、高速になる
- Indexのサイズが大きくなるので、更新のコストが高くなる
- Limit句を使うときにも効果がある
というメリットデメリットがあるそうです。
- メモリを十分に確保してダイレクトI/Oを活用する
- オンライン処理のあとに、バッチ処理で巨大なテーブルに対してフルスキャンするのは問題がある
- バッチ処理によるバッファプールが占有され、オンラインのバッファプールが追い出されてしまうため
- OOM Killerに注意する
- ダイレクトI/Oを使うとプロセス内にデータが置かれるので、プロセスのサイズが大きくなる
- DBサーバとしてはファイルシステムキャッシュを縮小してほしい
- # echo 0 > /proc/sys/vm/swappiness = 0
- -> Direct I/Oとセットで使うことが多い
- cpで大きいファイルをコピー
- cpに対してファイルシステムキャッシュが使われる
- InnoDBのプロセスのデータがスワップに追い出される->これは避けたい
- ファイルシステムはext3
- もっとも使われていて安全
- dir_index, noatime(relatime)
- xfsはDirect I/Oだと並列で書き込める
- xfsは巨大なファイルのコピーがはやい
- でもxfs使っている人少なすぎなので、おすすめできないw
- 監視の方法
- iotop: プロセス単位でI/O量を取る kernel 2.6.20以降
- ネットワーク統計: MySQL Cluster使う人には必要かも
- mtstat: 一秒おきに受信/送信byte数を表示
- /proc/net/dev をみればわかる情報
- SSD
- ランダムリードはHDD: 200回にたいして、25000回のI/O (Intel X25E)
- 書き込み性能は製品による差が激しい
- write cache必須
- バッテリーで守れていることが重要。RAIDコントローラに任せるものとSSD自身で持つものがある(RAID Controllerの場合はそれがSSDに対応していることが重要)
- SSDは並列性が重要。Crystalなんとかのベンチはシングルプロセスの話なのであてにならない
- PCI-E型SSDにも注目 -> I/Fの速度が速い(300MB -> 2GB)
途中から殴り書きですが、TIMESTAMP型が4byteとDATETIMEの半分で済むことにこの日初めて知りました。その他Covering Indexなど、知らなかったテクニックなのでとても勉強になりました。あとSSDは本当もうすぐそこまで来ていて、これを入れるだけで数倍DBのI/Oが速くなることを考えるとすごいなぁと。しかし色々ベンチ取られていて、すごく説得力のあるお話でした。この人にコンサル頼んだらいくらかかるんだろう…
2009/11/15 追記:tokuhiromさんのコメントを受けて、Text::MicroTemplate以外のテンプレートファイルにHTMLエスケープの処理を追加してベンチマークを取り直しています。
Perlのテンプレートエンジンでどれを使おうか悩んでいるので、Text::MicroTemplateをはじめとする以下のテンプレートエンジンの速度を簡単にベンチマークしてみました。
下記の要因で単純に比較できない部分もあるので、これを鵜呑みにしないでなるべく自身のユースケースの延長でもベンチマークを取ることをお奨めします。あくまで参考値ということで。
- そもそも持っている機能が全然違うので単純に比較できない
- 実際のテンプレートファイルはもっと複雑なはず
- utf8 flagの処理入れる場合もある
ではとりあえず結果から。
キャッシュなし
$ perl template_speed_file.pl 3000 0
Benchmark: timing 3000 iterations of HT, HT::Compiled, HT::Pro, MT, TT...
HT: 5 wallclock secs ( 4.60 usr + 0.09 sys = 4.69 CPU) @ 639.66/s (n=3000)
HT::Compiled: 10 wallclock secs (10.56 usr + 0.10 sys = 10.66 CPU) @ 281.43/s (n=3000)
HT::Pro: 1 wallclock secs ( 0.20 usr + 0.04 sys = 0.24 CPU) @ 12500.00/s (n=3000)
(warning: too few iterations for a reliable count)
MT: 2 wallclock secs ( 2.39 usr + 0.07 sys = 2.46 CPU) @ 1219.51/s (n=3000)
TT: 10 wallclock secs ( 9.65 usr + 0.17 sys = 9.82 CPU) @ 305.50/s (n=3000)
Rate HT::Compiled TT HT MT HT::Pro
HT::Compiled 281/s -- -8% -56% -77% -98%
TT 305/s 9% -- -52% -75% -98%
HT 640/s 127% 109% -- -48% -95%
MT 1220/s 333% 299% 91% -- -90%
HT::Pro 12500/s 4342% 3992% 1854% 925% --
キャッシュあり
コードを見てもらえばわかるのですが、「キャッシュあり」とは具体的には
- テンプレートのインスタンスを作るのは最初の1回だけ(キャッシュなしの場合は毎回作っている)
- テンプレートエンジンに対して、「キャッシュあり」とコンストラクタで指令
ということになります。
$ perl template_speed_file.pl 3000 1
Benchmark: timing 3000 iterations of HT, HT::Compiled, HT::Pro, MT, TT...
HT: 3 wallclock secs ( 2.69 usr + 0.00 sys = 2.69 CPU) @ 1115.24/s (n=3000)
HT::Compiled: 0 wallclock secs ( 0.28 usr + 0.00 sys = 0.28 CPU) @ 10714.29/s (n=3000)
(warning: too few iterations for a reliable count)
HT::Pro: 0 wallclock secs ( 0.13 usr + 0.02 sys = 0.15 CPU) @ 20000.00/s (n=3000)
(warning: too few iterations for a reliable count)
MT: 1 wallclock secs ( 0.22 usr + 0.01 sys = 0.23 CPU) @ 13043.48/s (n=3000)
(warning: too few iterations for a reliable count)
TT: 1 wallclock secs ( 1.84 usr + 0.00 sys = 1.84 CPU) @ 1630.43/s (n=3000)
Rate HT TT HT::Compiled MT HT::Pro
HT 1115/s -- -32% -90% -91% -94%
TT 1630/s 46% -- -85% -87% -92%
HT::Compiled 10714/s 861% 557% -- -18% -46%
MT 13043/s 1070% 700% 22% -- -35%
HT::Pro 20000/s 1693% 1127% 87% 53% --
- HTML::Template::Proが爆速!
- Text::MicroTemplateは、キャッシュなしだとそこそこ、
キャッシュありはかなり速い部類に入るようです。キャッシュありだとHTML::Template::Proには及ばないものの、かなり速いです。
- HTML::Template::Compiledはキャッシュを無効にするとTTより遅いという結果ですが、キャッシュを有効にすると猛烈に速くなるようです。ソースが追えてないのですが、中でガチンコにキャッシュしてるのでしょうか。
- たぶん一番使われているTTはよく「遅い」と言われていますが、キャッシュを有効にすると意外にもHTML::Templateより速くなりました。
というのがポイントだと思います。この結果だけで判断すると、HTML::Template::Proは神がかり的な速さなのですが、そもそもPerlのコードをテンプレートに記述できず自由度は低いので(*1)、使う場合にはそれなりに不便さを覚悟した方がよいと思います。「便利さ」という点だとText::MicroTemplateはテンプレート内にPerlのコードが書ける分かなり自由度が高い、かつ速度もいい感じでバランスが取れていると言えます。
*1) Perlのコードをテンプレートに書けてしまっていいのかどうかは使う人次第だと思いますが
以下にソースを載せておきます。テストに使ったマシンはUbuntu 9.04, perl 5.10.0, CPUが Athlon Dual Core Processor 5050e です。
template_speed_file.pl
#!/usr/bin/env perl
use strict;
use warnings;
use HTML::Template;
use HTML::Template::Compiled speed => 1;
use HTML::Template::Pro;
use Template;
use Text::MicroTemplate::File;
use Benchmark qw(timethese cmpthese);
my @LANGUAGES = (
{ language => 'Perl', ll => 1 },
{ language => 'Ruby', ll => 1 },
{ language => 'Python', ll => 1 },
{ language => 'PHP', ll => 1 },
{ language => 'Java' },
{ language => 'C' },
{ language => 'C++' },
{ language => 'C#' },
{ language => 'VB' },
{ language => 'VB.NET' },
{ language => 'ASP.NET' },
{ language => 'Delphi' },
{ language => 'Erlang' },
{ language => 'Scala', ll => 1 },
{ language => 'Go' },
);
my $count = shift @ARGV || 1000;
my $cache = shift @ARGV || 0;
my $mt = undef;
sub mt {
my %args = (use_cache => $cache);
if ($cache) {
$mt ||= Text::MicroTemplate::File->new(%args);
} else {
$mt = Text::MicroTemplate::File->new(%args);
}
my $s = $mt->render_file(
'template_speed_file.mt',
{
page_title => 'LL Programming languages',
languages => \@LANGUAGES,
}
)->as_string;
}
my $ht = undef;
sub ht {
my %args = (
filename => 'template_speed_file.ht',
case_sensitive => 1,
die_on_bad_params => 0,
cache => $cache,
);
if ($cache) {
$ht ||= HTML::Template->new(%args);
} else {
$ht = HTML::Template->new(%args);
}
$ht->param(
page_title => 'LL Programming languages',
languages => \@LANGUAGES,
);
my $s = $ht->output;
}
my $htc = undef;
sub htc {
my %args = (
filename => 'template_speed_file.ht',
case_sensitive => 1,
die_on_bad_params => 0,
cache => $cache,
);
if ($cache) {
$htc ||= HTML::Template::Compiled->new(%args);
} else {
$htc = HTML::Template::Compiled->new(%args);
}
$htc->param(
page_title => 'LL Programming languages',
languages => \@LANGUAGES,
);
my $s = $htc->output;
}
my $htp = undef;
sub htp {
my %args = (
filename => 'template_speed_file.ht',
case_sensitive => 1,
die_on_bad_params => 0,
cache => $cache,
);
if ($cache) {
$htp ||= HTML::Template::Pro->new(%args);
} else {
$htp = HTML::Template::Pro->new(%args);
}
$htp->param(
page_title => 'LL Programming languages',
languages => \@LANGUAGES,
);
my $s = $htp->output;
}
my $tt = undef;
sub tt {
if ($cache) {
$tt ||= Template->new(
CACHE_SIZE => $cache ? undef : 0,
);
} else {
$tt = Template->new;
}
$tt->process(
'template_speed_file.tt',
{
page_title => 'LL Programming languages',
languages => \@LANGUAGES,
},
\my $out,
) or die $tt->error() . "\n";
}
# main
my $comp = timethese(
$count,
{
'MT' => \&mt,
'HT' => \&ht,
'HT::Pro' => \&htp,
'HT::Compiled' => \&htc,
'TT' => \&tt,
}
);
cmpthese $comp;
template_speed_file.mt – Text::MicroTemplate用のファイル
? my $p = $_[0];
<html>
<head><title>Programming languages</title></head>
<body>
<h1><?= $p->{page_title} ?></h1>
<br />
<ul>
? for my $language (@{ $p->{languages} }) {
<li><?= $language->{language} ?></li>
? }
</ul>
</body>
</html>
template_speed_file.ht – HTML::Template::*用のファイル
<html>
<head><title>Programming languages</title></head>
<body>
<h1><TMPL_VAR NAME=page_title ESCAPE=HTML></h1>
<br />
<ul>
<TMPL_LOOP NAME=languages>
<li><TMPL_IF NAME=ll>*</TMPL_IF><TMPL_VAR NAME=language ESCAPE=HTML></li>
</TMPL_LOOP>
</ul>
</body>
</html>
template_speed_file.tt – Template::Toolkit用のファイル
<html>
<head><title>Programming languages</title></head>
<body>
<h1>[% page_title | html %]</h1>
<br />
<ul>
[% FOREACH language IN languages %]
<li>[% language.language | html %]</li>
[% END %]
</ul>
</body>
</html>
HTMLエスケープあり/なしのスピードについて
tokuhiromさんのコメントにあるように、Text::MicroTemplateは自動でHTMLエスケープがかかる一方、他のテンプレートエンジンでは手動でやる必要があるので、HTMLエスケープを入れて測り直してみました。ちなみに上に載せているベンチの結果は、HTMLエスケープを入れたものになっています。
特筆すべきなのは、HTML::Template::CompiledがキャッシュありかつHTMLエスケープありの場合、HTMLエスケープなしに比べてパフォーマンスがかなり劣化している点です。(その他はちょっとずつパフォーマンスが劣化していますね)。
キャッシュなしかつHTMLエスケープなし
$ perl template_speed_file.pl 3000 0
Benchmark: timing 3000 iterations of HT, HT::Compiled, HT::Pro, MT, TT...
HT: 4 wallclock secs ( 3.85 usr + 0.10 sys = 3.95 CPU) @ 759.49/s (n=3000)
HT::Compiled: 10 wallclock secs ( 9.99 usr + 0.09 sys = 10.08 CPU) @ 297.62/s (n=3000)
HT::Pro: 1 wallclock secs ( 0.21 usr + 0.02 sys = 0.23 CPU) @ 13043.48/s (n=3000)
(warning: too few iterations for a reliable count)
MT: 2 wallclock secs ( 2.35 usr + 0.05 sys = 2.40 CPU) @ 1250.00/s (n=3000)
TT: 8 wallclock secs ( 7.83 usr + 0.15 sys = 7.98 CPU) @ 375.94/s (n=3000)
Rate HT::Compiled TT HT MT HT::Pro
HT::Compiled 298/s -- -21% -61% -76% -98%
TT 376/s 26% -- -51% -70% -97%
HT 759/s 155% 102% -- -39% -94%
MT 1250/s 320% 233% 65% -- -90%
HT::Pro 13043/s 4283% 3370% 1617% 943% --
キャッシュなしかつHTMLエスケープあり
Benchmark: timing 3000 iterations of HT, HT::Compiled, HT::Pro, MT, TT...
HT: 5 wallclock secs ( 4.60 usr + 0.09 sys = 4.69 CPU) @ 639.66/s (n=3000)
HT::Compiled: 10 wallclock secs (10.56 usr + 0.10 sys = 10.66 CPU) @ 281.43/s (n=3000)
HT::Pro: 1 wallclock secs ( 0.20 usr + 0.04 sys = 0.24 CPU) @ 12500.00/s (n=3000)
(warning: too few iterations for a reliable count)
MT: 2 wallclock secs ( 2.39 usr + 0.07 sys = 2.46 CPU) @ 1219.51/s (n=3000)
TT: 10 wallclock secs ( 9.65 usr + 0.17 sys = 9.82 CPU) @ 305.50/s (n=3000)
Rate HT::Compiled TT HT MT HT::Pro
HT::Compiled 281/s -- -8% -56% -77% -98%
TT 305/s 9% -- -52% -75% -98%
HT 640/s 127% 109% -- -48% -95%
MT 1220/s 333% 299% 91% -- -90%
HT::Pro 12500/s 4342% 3992% 1854% 925% --
キャッシュありかつHTMLエスケープなし
$ perl template_speed_file.pl 3000 1
Benchmark: timing 3000 iterations of HT, HT::Compiled, HT::Pro, MT, TT...
HT: 3 wallclock secs ( 2.50 usr + 0.00 sys = 2.50 CPU) @ 1200.00/s (n=3000)
HT::Compiled: 0 wallclock secs ( 0.15 usr + 0.00 sys = 0.15 CPU) @ 20000.00/s (n=3000)
(warning: too few iterations for a reliable count)
HT::Pro: 0 wallclock secs ( 0.10 usr + 0.04 sys = 0.14 CPU) @ 21428.57/s (n=3000)
(warning: too few iterations for a reliable count)
MT: 0 wallclock secs ( 0.21 usr + 0.00 sys = 0.21 CPU) @ 14285.71/s (n=3000)
(warning: too few iterations for a reliable count)
TT: 1 wallclock secs ( 1.19 usr + 0.01 sys = 1.20 CPU) @ 2500.00/s (n=3000)
Rate HT TT MT HT::Compiled HT::Pro
HT 1200/s -- -52% -92% -94% -94%
TT 2500/s 108% -- -82% -87% -88%
MT 14286/s 1090% 471% -- -29% -33%
HT::Compiled 20000/s 1567% 700% 40% -- -7%
HT::Pro 21429/s 1686% 757% 50% 7% --
キャッシュありかつHTMLエスケープあり
Benchmark: timing 3000 iterations of HT, HT::Compiled, HT::Pro, MT, TT...
HT: 3 wallclock secs ( 2.69 usr + 0.00 sys = 2.69 CPU) @ 1115.24/s (n=3000)
HT::Compiled: 0 wallclock secs ( 0.28 usr + 0.00 sys = 0.28 CPU) @ 10714.29/s (n=3000)
(warning: too few iterations for a reliable count)
HT::Pro: 0 wallclock secs ( 0.13 usr + 0.02 sys = 0.15 CPU) @ 20000.00/s (n=3000)
(warning: too few iterations for a reliable count)
MT: 1 wallclock secs ( 0.22 usr + 0.01 sys = 0.23 CPU) @ 13043.48/s (n=3000)
(warning: too few iterations for a reliable count)
TT: 1 wallclock secs ( 1.84 usr + 0.00 sys = 1.84 CPU) @ 1630.43/s (n=3000)
Rate HT TT HT::Compiled MT HT::Pro
HT 1115/s -- -32% -90% -91% -94%
TT 1630/s 46% -- -85% -87% -92%
HT::Compiled 10714/s 861% 557% -- -18% -46%
MT 13043/s 1070% 700% 22% -- -35%
HT::Pro 20000/s 1693% 1127% 87% 53% --
コメント