アーカイブ

‘Perl’ カテゴリーのアーカイブ

コマンドラインオプションを解析するPerlモジュールGetopt::Compact

2010 年 4 月 10 日 oinume コメントはありません

Perlでコマンドラインオプションを解析する場合、大体は標準添付されているGetopt::Longを使うと思いますが、自分は3年前ぐらいから Getopt::Compact というモジュールに出会い、それ以降大抵の場合はこれを使っています。このモジュールの良いところは

  • オプションの定義(-c, –config などの定義)とそのヘルプメッセージが一箇所で定義できる
  • -h, –helpオプションを最初から定義してくれてる(–manオプションも)
  • 定義してないオプションが指定されると自動的にエラーにしてくれる
  • Getopt::Compact#usage でヘルプメッセージが簡単に取得できる

があると思っています。などと抽象的な話をしてもしょうがないので、具体的なコードを書いてみます。

#!/usr/bin/env perl
 
use strict;
use warnings;
use Getopt::Compact;
use Data::Dumper;
 
my $getopt = Getopt::Compact->new(
    name => 'getopt_compact.pl',
    modes => [ qw(verbose) ],
    args => 'FILE',
    version => '0.1',
    struct => [
        [ [ qw(f force) ], 'force overwrite of output file and compress links' ],
        [ [ qw(l level) ], 'compress level', '=i' ],
        [ [ qw(S suffix) ], 'use suffix on compressed files', '=s' ],
    ]
);
 
my $options = $getopt->opts;
my $file = shift @ARGV;
unless ($file) {
    print STDERR $getopt->usage;
    exit 1;
}
 
print "options -----\n";
print Dumper $options;

上の例では gzip のオプションのモノマネで

  • -f, –forceというフラグオプション
  • -l, –levelという数値のオプション
  • -S, –suffixという文字列のオプション
  • オプション以外に引数FILEを受け取る

という仕様としています。

でで、例えば

$ perl getopt_compact.pl --help

と実行すると

getopt_compact.pl v0.1
usage: getopt_compact.pl [options] FILE
options                                                          
-h, --help      This help message                                
-v, --verbose   Verbose mode                                     
-f, --force     Force overwrite of output file and compress links
-l, --level     Compress level                                   
-S, --suffix    Use suffix on compressed files                   
    --man       Display documentation

のようにヘルプが表示されます。また、–hoge のような存在しないオプションを指定すると

$ perl getopt_compact.pl --hoge
Unknown option: hoge
getopt_compact.pl v0.1
usage: getopt_compact.pl [options] FILE
options                                                          
-h, --help      This help message                                
-v, --verbose   Verbose mode                                     
-f, --force     Force overwrite of output file and compress links
-l, --level     Compress level                                   
-S, --suffix    Use suffix on compressed files                   
    --man       Display documentation

のようにエラーになりヘルプが表示されます。

という感じで、Getopt::Longより依存モジュールがあるものの便利に使わせてもらってます。ただ、PerlのGetopt系のモジュールは他にも色々あるのでもっと便利なのがありそうですが、こういうのもあるよという紹介でした。

モダンPerl入門 (CodeZine BOOKS)

著者/訳者:牧 大輔

出版社:翔泳社( 2009-02-10 )

大型本 ( 344 ページ )


カテゴリー: Perl タグ:

DBI->connectのHandleError

2010 年 2 月 11 日 oinume コメントはありません

DBIのちょっとしたTIPSです。DBIには connect する時に

DBI->connect('dbi:mysql:database=hoge', 'root', 'whatever', { ... });

とオプションを渡すことができます。(たとえば RaiseError) 。このオプションのひとつに HandleError というものがあってデバッグにはなかなか便利なので紹介してみます。端的にいうと HandleError でサブルーチンを登録しておくと、エラーが発生したときにこのサブルーチンを使ってエラーを投げてくれるようになります。以下は具体例。

# MyDB.pm

package MyDB;
 
use strict;
use warnings;
use Carp ();
use DBI;
 
sub new {
    bless {}, shift;
}
 
sub connect {
    my ($self) = @_;
    $self->{dbh} = DBI->connect(
        'dbi:mysql:database=dbix_thin_test', 'root', 'root',
        { RaiseError => 1, HandleError => \&Carp::confess } # here
    );
}
 
sub select {
    my ($self, $sql) = @_;
    my $sth = $self->{dbh}->prepare($sql); # error!
    $sth->execute();
}
 
1;

# dbi_handle_error.pl

#!/usr/bin/env perl
 
use strict;
use warnings;
use FindBin qw($Bin);
use lib "$Bin";
use MyDB;
 
my $db = MyDB->new;
$db->connect();
$db->select("select * from not_exist");

と書いてわざと MyDB#select でエラーになるようにしておき、このスクリプトを実行します。
すると

$ perl ~/script/perl/dbi_handle_error.pl
DBD::mysql::st execute failed: Table 'dbix_thin_test.not_exist' doesn't existDBI::st=HASH(0x248a900) at /home/kazuhiro/script/perl/MyDB.pm line 23
        MyDB::select('MyDB=HASH(0x21b8df0)') called at /home/kazuhiro/script/perl/dbi_handle_error.pl line 11

のように DBI->connectの HandleError で渡した Carp::confess が呼ばれ、エラーになった時にスタックトレースが表示されます。HandleError を指定しない場合は

$ perl ~/script/perl/dbi_handle_error.pl
DBD::mysql::st execute failed: Table 'dbix_thin_test.not_exist' doesn't exist at /home/kazuhiro/script/perl/MyDB.pm line 23.
DBD::mysql::st execute failed: Table 'dbix_thin_test.not_exist' doesn't exist at /home/kazuhiro/script/perl/MyDB.pm line 23.

のようになってしまい、実際にエラーが発生している箇所と問題のクエリの関連性がわかりにくいですが、HandleErrorで Carp::confess を指定しておくとファイル名と行番号がわかるのでどこでエラーになっているかあたりがつけやすいと思います。(この例だとメソッドのネストが少ないのでそのありがたみがわからないですが…)

以上、生DBIでもこんなマニアックなオプションがあるんだよという紹介でした。ZIGOROuさんの生 DBI ユーザーのための DBI Cookbook (1)を見ててよく思うのですが、DBIってなかなか奥が深いです。

カテゴリー: Perl タグ:

DBIx::ThinをCPANから削除

2010 年 1 月 13 日 oinume コメントはありません

表題の通りですが、思うところがあってDBIx::Thinを削除しました。きっかけは、CPAN Ratingsのレビューです(以下引用)。

DBIx-Thin (0.05) **

It strikes me as odd how almost most of Japanese authors (of course, except honourable and admirable authors) don’t write why they have written their modules and the intention of the modules.
So is the author of this module.
The module is yet another ORM module but unfocused one. What the heck is different from DBIx::Skinny? The explanation is nowhere to be found, but there is just an enumeration of methods. And yet could anyone else use the module?

言われてみればその通りで、PODにコンセプトも書いてなければDBIx::Skinnyと違うところも良くわからないと。その辺に関してはモジュールのPODに書いておきましたが、それでもDBIx::Skinnyとあんまり変わらないし、現時点で自分以外の誰かが使っているとも思えないので、変な混乱を招かないようにCPANからは削除しました。んでgithubでは開発を継続していきます。

カテゴリー: DBIx::Thin タグ: ,

DBIx::ThinというORMapperをリリースしました

2009 年 12 月 29 日 oinume コメントはありません

今年のYAPC(9月)からすき間時間を使って作っていたDBIx::Thinという、DBIx::Skinnyインスパイアなモジュールを昨日やっとリリースしました。作った動機は単に自己満足の追求と車輪の再発明による個人のスキルアップですが、前職で3年前ぐらいに自作したORMのコードを忘れないうちに改良して世に出したいなぁとずっと思っていたのです。SkinnyのAdvent Calendarが日々更新されているのを読み、途中で何度も「これSkinny使えばいいんじゃね?」って思って挫折しかけましたが、Skinnyも細かいところでは自分のポリシーと合わない部分があったりしたのと、とりあえず作ってしまったので世に出してみます。

特徴としては

  • 生SQL派にやさしい
  • Skinnyより書き方は冗長だと思うけど、コードを見て何をやっているかわかるようにした
  • 依存モジュールが少ない(標準以外のものは3つ)
  • Schemaクラスを生成するジェネレータが標準添付(ただしまだMySQL以外のDBに対応していない)

かなと思います。SQLはなんだかんだ言ってもまだWebエンジニアの共通言語だと思うので、ORMでゴリゴリJOINのコードを書くより生SQLの方がわかりやすい、と思っているので、生SQLなモジュールを作りました。

「Skinnyより書き方は冗長」というのは、例えばSkinnyだと

my $iterator = Your::Model->search(
    'user',
    {id => 1},
    {order_by => 'id'}
);

と書きますが、2つ目の引数が何を表しているのかぱっと見わからないので、Thinの場合は

my $iterator = Your::Model->search(
    'user',
    where => {id => 1}, # これ
    order_by => 'id',
);

と書くようにしています。もちろんSkinnyも学習すれば全然わかると思うので、細かいどうでもいいことかもしれませんが…

あとSkinnyと違う点は、

  • SchemaクラスとレコードのRowクラスは分離されていますが、シンプルさを重視してあえて一緒のクラスにまとめているところ(Your::Model::UserみたいなクラスがSchema定義をしてさらにこれのインスタンスがRowオブジェクト)
  • Schemaクラスの書き方がSkinnyはinflateやutf8がルールベースで全テーブルに適用されますが、こっちは基本的にテーブルの各カラムごとに書くようにさせてます

ところぐらいでしょうか。

最後に、Skinnyのコードはすごく綺麗で色々な部分を参考にさせてもらいました。作者のnekokakさん、どうもありがとうございました。

カテゴリー: DBIx::Thin タグ: ,

PerlのDateTimeとTime::Pieceモジュールのベンチマーク

2009 年 12 月 6 日 oinume コメントはありません

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,
    };
}
カテゴリー: Perl タグ:

Text::MicroTemplate 0.10で速度が速くなっている件

2009 年 11 月 21 日 oinume コメントはありません

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 タグ:

Text::MicroTemplateとHTML::Template::Proの比較

2009 年 11 月 21 日 oinume コメントはありません

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 機能でなんとかできるのかもしれませんが、まだそこまでは考えられてないです。というわけで相変わらずテンプレートエンジンどうしようか悩み中…

カテゴリー: Perl タグ:
Pages: 1 2 >>