amari3のはてなダイアリー このページをアンテナに追加 RSSフィード

2011-12-16

[][][]Perl の Log 系モジュールのベンチマークを取ってみた

今年の YAPC や他のセミナーでも、ログの重要性をよく聞いたので、ログをしっかり取るようにしようと考えた。そこで Perl の主要(だと思われる)Log 系のモジュールのベンチマークを取ってみた。

環境及びモジュール

  • Perl-5.14.2
  • Log::Dispatch-2.29
  • Log::Hanlder-0.71
  • Log::Minimal-0.09
  • warn

前提条件

  • 基本的にはほとんどカスタマイズせずに実行
    • 出力される文字数等にバラツキが出るがデフォルトの状態で比較したいので
  • 出力先は標準エラー

ベンチマーク用のスクリプト

use strict;
use warnings;
use Benchmark qw(timethese cmpthese);
use Log::Handler;
use Log::Dispatch;
use Log::Minimal;

my $count = shift || 1000;

# Log::Handler
my $logh = Log::Handler->new();
$logh->add(
    screen => +{
        log_to   => 'STDERR',
        maxlevel => 'debug',
    }
);

# Log::Dispatch
my $logd = Log::Dispatch->new(outputs => [[
    "Screen",
    min_level => 'debug',
    stderr    => 1,
    newline   => 1,
]]);

my $result = timethese($count, +{
    log_handler  => sub { $logh->info('log'); },
    log_dispatch => sub { $logd->info('log'); },
    log_minimal  => sub { infof('log');  },
    warn         => sub { warn "log\n"; },
});

cmpthese $result;

実行結果

Benchmark: timing 10000 iterations of log_dispatch, log_handler, log_minimal, warn...
log_dispatch:  2 wallclock secs ( 1.22 usr +  0.13 sys =  1.35 CPU) @ 7407.41/s (n=10000)
log_handler:  1 wallclock secs ( 0.54 usr +  0.24 sys =  0.78 CPU) @ 12820.51/s (n=10000)
log_minimal:  1 wallclock secs ( 0.30 usr +  0.15 sys =  0.45 CPU) @ 22222.22/s (n=10000)
      warn:  0 wallclock secs ( 0.01 usr +  0.04 sys =  0.05 CPU) @ 200000.00/s (n=10000)
            (warning: too few iterations for a reliable count)
                 Rate log_dispatch  log_handler  log_minimal         warn
log_dispatch   7407/s           --         -42%         -67%         -96%
log_handler   12821/s          73%           --         -42%         -94%
log_minimal   22222/s         200%          73%           --         -89%
warn         200000/s        2600%        1460%         800%           --

標準の warn が圧倒的に早いのは当然として、Log::Minimal のスコアが良い感じです。

まとめ

今回のベンチマークの結果を見て、Log::Minimal を採用しようと考えてる。速度面以外でも

  • オブジェクトの生成の必要がなくカジュアルに使える
  • ログレベルが適度な数でどれを使うか迷う事が少なくなる(気がする)
  • 覚えることが少ないんで他のエンジニアも簡単に使える(と思う)

といったところが大きいかなと。

2011-12-10

[][][][]PerlMicrosoft Translator API を使って翻訳するコードを書いてみた

Microsoft が提供している、Microsoft Translator API を Perl からコールして翻訳するコードを書いた。man page とか perldoc で英語を読む時に、分からない文章をコピーして、翻訳サイトに貼り付けるのが面倒くさくなったんで、コマンドラインから翻訳出来たら便利だなと思って作ってみた。

ソースコードgithub に上げておきました - https://github.com/amari3/p5-translate.pl

デベロッパー登録

API にアクセスするためには、Windows Live の登録とアプリケーションを登録して、アクセスキー(アプリケーションID)を取得する必要がある。

こちらから登録が出来ます - http://www.bing.com/developers/createapp.aspx

デベロッパー用サイト

開発者向けのサイト。ドキュメント等もこちらから閲覧が出来る。

http://www.bing.com/developers

http://www.microsofttranslator.com/dev/

使い方

git clone 等で取り込んで、以下の様にコマンドを実行するだけ。CPAN モジュールの WebService::Simple と Config::Pit は入ってない場合は入れる必要があります。また初回実行時のみアクセスキーの入力が求められます。

デフォルトは英語→日本語の翻訳。

% ./translate.pl "I love sushi."
寿司が大好きです。

オプション指定で日本語→英語の翻訳

% ./translate.pl --from=ja --to=en "チョコレートが食べたいです"
I want to eat is chocolate

オプション指定で日本語→フランス語の翻訳

% ./translate.pl --from=ja --to=fr "チョコレートが食べたいです"
Je veux manger est au chocolat

コマンドラインから簡単に翻訳ができて便利です。

今後やりたいこと

とりあえず、今後やりたいことをまとめてみた。

  • Microsoft Translator API を簡単に使うためのモジュール化
    • WebService::Microsoft::Translator みたいなモジュールを作りたい
  • ヘルプ機能の実装
    • サポートしている言語の一覧表示など
  • モダンな感じに書き直す

まとめ

github にソースコードを上げるのも初めてだし、Web API を仕事以外で利用するのも多分初めて。色々至らない点が多いと思うけど、積極的にアウトプットしていきたいと考えている。つっこみ等がありましたら、コメントやブクマコメントしてもらえると嬉しいです。

2011-12-02

[][][]Teng で検索あれこれ

前回までの説明で、Teng を何となく使えるようになると思う。今回は Teng が提供する検索メソッドについて触れてなかった箇所についての説明をする。

Teng の検索メソッド

以下の4つのメソッドが提供されている。

  • Teng#search
  • Teng#single
  • Teng#search_named
  • Teng#search_by_sql

上2つのメソッドは、Teng で CRUD をしてみる - amari3のはてなダイアリー で説明しているのでこちらを参照ください。

また、説明に使用するテーブルやスキーマ等は、こちら Teng でリレーションを使う方法 - amari3のはてなダイアリー と同じものです。

Teng#search_named メソッド

Teng#search_named メソッドは生(に近い)SQL を記述する時に便利なメソッド。Teng::Iterator オブジェクトが返ってくる。

サンプルコードです。

my $it = $teng->search_named(
    q{SELECT * FROM entry WHERE ( id IN :ids )},
    +{ ids => [2, 4] }
);

少し見慣れない表記が含まれているけど、実際には以下の様な SQL 文になる。

SELECT * FROM entry WHERE ( id IN ( ?,? ) )
bind [2,4]

IN 演算子の値の個数が動的に変わる場合でも、呼び出し側は意識せずに利用できて便利。

Teng#search_by_sql メソッド

Teng#search_by_sql メソッドは生 SQL 文を記述することができる。Teng::Iterator オブジェクトが返ってくる。

サンプルコードです。

my $it = $teng->search_by_sql(
    q{SELECT * FROM entry WHERE id > ?},
    [ 2 ]
);

普通に SQL 文が記述できるので、難しいところは無いと思う。

どんな時に使うのか

今回例示したケースでは使う必要は無いと考える。主に以下の様なケースで使うといいと思う。

  • 集計バッチ等で複雑な SQL 文を記述する必要があるとき

Teng で WHERE 句の条件やソート条件

ここからは Teng で WHERE 句の条件の記述方法やソート条件の記述方法を説明をする。Teng でと銘打ってはいるけど、Teng のクエリビルダである SQL::Maker の機能の説明になるので、SQL::Maker のドキュメントもあわせて読むのが良いでしょう。

BETWEEN 演算子を使う

範囲検索でよく使う BETWEEN 演算子の記述方法です。

my $it = $teng->search(entry => +{ id => +{ between => [2, 4] } });

カラム名に、ハッシュリファレンスで条件を記述することになる。難しいところは無いと思う。『>』や『!=』等も基本的には同じように記述する。

ORDER BY 句を使う

検索結果のソートをする、order by の記述方法です。

my $it = $teng->search(entry => +{ id => +{ '>' => 2 }}, +{ order_by => 'id DESC' });

Teng#search メソッドの第3引数に記述することで実現できる。こちらも難しいところは無いと思う。

最後に

今回は検索メソッドについて、少しだけ踏み込んだ説明をしました。複雑な検索方法も分かってきたので、業務や趣味プログラムでそろそろ使いたいと思います。

2011-12-01

[][][]Teng でリレーションを使う方法

前回は Teng でトランザクション処理をする方法を紹介しました。実際に色々試して、ブログに書くと頭にいい感じで入ってくるので続けていきたいです。今回は Teng でリレーションを使う方法を紹介していきます。

題材

説明に使用する題材は、掲示板へのエントリとそれに対するコメントの様なものを想定。

使用するテーブル

以下の2つのテーブルを使用します。

test@localhost:testdb> desc entry;
+------------+------------------+------+-----+---------------------+----------------+
| Field      | Type             | Null | Key | Default             | Extra          |
+------------+------------------+------+-----+---------------------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL                | auto_increment |
| name       | varchar(64)      | NO   |     | NULL                |                |
| title      | varchar(128)     | NO   |     | NULL                |                |
| body       | text             | NO   |     | NULL                |                |
| created_at | datetime         | NO   |     | 0000-00-00 00:00:00 |                |
+------------+------------------+------+-----+---------------------+----------------+
5 rows in set (0.00 sec)

test@localhost:testdb> desc comment;
+--------------+------------------+------+-----+---------------------+----------------+
| Field        | Type             | Null | Key | Default             | Extra          |
+--------------+------------------+------+-----+---------------------+----------------+
| id           | int(10) unsigned | NO   | PRI | NULL                | auto_increment |
| entry_id     | int(10) unsigned | NO   | MUL | NULL                |                |
| name         | varchar(64)      | NO   |     | NULL                |                |
| body         | text             | NO   |     | NULL                |                |
| is_invisible | tinyint(4)       | NO   |     | 0                   |                |
| created_at   | datetime         | NO   |     | 0000-00-00 00:00:00 |                |
+--------------+------------------+------+-----+---------------------+----------------+
6 rows in set (0.00 sec)

使用するデータ

すでに以下のデータが入れてあります。

test@localhost:testdb> select * from entry;
+----+-----------------+-----------------------+--------------------------------------------------------+---------------------+
| id | name            | title                 | body                                                   | created_at          |
+----+-----------------+-----------------------+--------------------------------------------------------+---------------------+
|  1 | amari3          | 肉まん食べたい        | 肉まんが食べたいでござる                               | 2011-11-30 15:07:41 |
|  2 | あまりさん      | シュークリーム        | シュークリームがいっぱい食べたいよ!                   | 2011-11-30 15:07:41 |
|  3 | amari3          | 魔法ジュース          | 魔法ジュース久しぶりに飲みたいなぁ                     | 2011-11-30 15:07:41 |
+----+-----------------+-----------------------+--------------------------------------------------------+---------------------+
3 rows in set (0.03 sec)
test@localhost:testdb> select * from comment;
+----+----------+-----------------+-----------------------------+--------------+---------------------+
| id | entry_id | name            | body                        | is_invisible | created_at          |
+----+----------+-----------------+-----------------------------+--------------+---------------------+
|  1 |        2 | 名無しさん      | 俺も俺も                    |            0 | 2011-11-30 15:07:41 |
|  2 |        2 | マヒャド        | うまいよねー                |            0 | 2011-11-30 15:07:41 |
|  3 |        2 | ヒャダルコ      | たまに食いたくなる          |            0 | 2011-11-30 15:07:41 |
|  4 |        3 | 名無しさん      | 何それ!                    |            0 | 2011-11-30 15:07:41 |
+----+----------+-----------------+-----------------------------+--------------+---------------------+
4 rows in set (0.00 sec)

モデルクラス

Teng を使うためのモデルクラスを定義します。

package My::DB;
use parent 'Teng';

1;

スキーマクラス

テーブル情報を持つスキーマクラスを定義します。

package My::DB::Schema;
use Teng::Schema::Declare;
use DateTime::Format::MySQL;

table {
    name 'entry';
    pk 'id';
    columns qw( id name title body created_at );

    inflate 'created_at' => sub {
        DateTime::Format::MySQL->parse_datetime(shift);
    };
    deflate 'created_at' => sub {
        DateTime::Format::MySQL->format_datetime(shift);
    };
};

table {
    name 'comment';
    pk 'id';
    columns qw( id entry_id name body is_invisible created_at );

    inflate 'created_at' => sub {
        DateTime::Format::MySQL->parse_datetime(shift);
    };
    deflate 'created_at' => sub {
        DateTime::Format::MySQL->format_datetime(shift);
    };
};

1;

上記の様に、entry/comment のテーブル情報を定義します。

entry/comment テーブルのリレーションシップを設定

Teng でリレーション設定をするには、Teng::Row(以下、Rowオブジェクト) へメソッドを追加することで実現できます。

entry.id と comment.entry_id がそれぞれのテーブルで関係する ID となるので、これで関連付けをします。関連付けをすることにより、あるエントリに対するコメント一覧等の取得が簡単にできるようになります。


任意の entry に関連する comment のイテレータを返すメソッドを定義

entry → comment は1対多の has_many なリレーションとなります。

package My::DB::Row::Entry;
use strict;
use warnings;
use parent 'Teng::Row';

sub to_comments {
    my $self = shift;
    $self->{teng}->search(comment => +{ entry_id => $self->id });
}

1;
comment の Rowオブジェクトに関連する entry を取得するメソッドを定義

comment → entry は多対1 のbelongs_to なリレーションとなります。

package My::DB::Row::Comment;
use strict;
use warnings;
use parent 'Teng::Row';

sub to_entry {
    my $self = shift;
    $self->{teng}->single(entry => +{ id => $self->entry_id });
}

1;

サンプルコードと実行結果

これまででリレーションの設定ができたので、実際に使ってみます。

まずは has_many なリレーションのサンプルコードです。

use strict;
use warnings;
use utf8;
use feature qw( say );

use FindBin;
use lib "$FindBin::Bin/lib";
use My::DB;

my $teng = My::DB->new(connect_info => [
    'dbi:mysql:database=testdb',
    'test',
    'tes10',
    +{
        RaiseError => 1,
        PrintError => 0,
        AutoCommit => 1,
        on_connect_do => [
            "SET NAMES 'utf8'",
            "SET CHARACTER SET 'utf8'",
        ],
    },
]);

my $entry = $teng->single(entry => +{ id => 2 });
say "entry.id:         ", $entry->id,    "\n",
    "entry.name:       ", $entry->name,  "\n",
    "entry.title:      ", $entry->title, "\n",
    "entry.body:       ", $entry->body,  "\n",
    "entry.created_at: ", $entry->created_at;
say "----";

my $it = $entry->to_comments;
while (my $comment = $it->next) {
    say "comment.id:           ", $comment->id,           "\n",
        "comment.entry_id:     ", $comment->entry_id,     "\n",
        "comment.name:         ", $comment->name,         "\n",
        "comment.body:         ", $comment->body,         "\n",
        "comment.is_invisible: ", $comment->is_invisible, "\n",
        "comment.created_at:   ", $comment->created_at;
    say "--";
}

実行結果です。

entry.id:         2
entry.name:       あまりさん
entry.title:      シュークリーム
entry.body:       シュークリームがいっぱい食べたいよ!
entry.created_at: 2011-11-30T15:07:41
----
comment.id:           1
comment.entry_id:     2
comment.name:         名無しさん
comment.body:         俺も俺も
comment.is_invisible: 0
comment.created_at:   2011-11-30T15:07:41
--
comment.id:           2
comment.entry_id:     2
comment.name:         マヒャド
comment.body:         うまいよねー
comment.is_invisible: 0
comment.created_at:   2011-11-30T15:07:41
--
comment.id:           3
comment.entry_id:     2
comment.name:         ヒャダルコ
comment.body:         たまに食いたくなる
comment.is_invisible: 0
comment.created_at:   2011-11-30T15:07:41
--

続いて、belongs_to なリレーションのサンプルコードです。(重複するコードは割愛)

my $it = $teng->search(comment => +{ entry_id => 2 });
while (my $comment = $it->next) {
    my $entry = $comment->to_entry;
    say "entry.name:         ", $entry->name,   "\n",
        "entry.title:        ", $entry->title,  "\n",
        "comment.id:         ", $comment->id,   "\n",
        "comment.name:       ", $comment->name, "\n",
        "comment.body:       ", $comment->body, "\n",
        "comment.created_at: ", $comment->created_at;
    say "--";
}

実行結果です。

entry.name:         あまりさん
entry.title:        シュークリーム
comment.id:         1
comment.name:       名無しさん
comment.body:       俺も俺も
comment.created_at: 2011-11-30T15:07:41
--
entry.name:         あまりさん
entry.title:        シュークリーム
comment.id:         2
comment.name:       マヒャド
comment.body:       うまいよねー
comment.created_at: 2011-11-30T15:07:41
--
entry.name:         あまりさん
entry.title:        シュークリーム
comment.id:         3
comment.name:       ヒャダルコ
comment.body:       たまに食いたくなる
comment.created_at: 2011-11-30T15:07:41
--

最後に

Teng でリレーションを使うのは、非常に簡単だということ分かりました。今回は 1対1 や、多対多のリレーションは扱いませんでしたが、同じ様に簡単に扱えると思います。

2011-11-28

[][]Perlにおける設定ファイルを自分なりに比較してみた

Twitterで10日くらい前に、Perlから設定ファイルを扱う際に、どの形式を使うのがいいんだろう?みたいな感じなつぶやきをしていたら、フォロワーの方からリプライが付いたので、メジャーな設定ファイル(と思われる)をそれぞれ自分なりに比較検討してみたのでそのまとめ。

以下の3つの形式を比較検討。

  • ini形式
  • yaml形式
  • perl形式

ini/yaml形式のサンプルコードでは、CPANモジュールを使っているが、モジュールの選択基準はあまりなく、とりあえず仕様が小さそうくらいな感じのを選択。今回はあくまで設定ファイルに特化したいと考えている。

ini形式

Windowsでも割と一般的に見かける形式。mysqlの設定ファイルもこの形式。

設定ファイル例

% cat data.ini
[site1]
url = http://d.hatena.ne.jp/amari3
title = amari3のはてなダイアリー
author = amari3

[site2]
url = http://amari3.hatenablog.jp
title = amari3の日記
author = amari3

設定ファイル読み込み例

use strict;
use warnings;
use feature qw( say );
use Config::Tiny;
use Data::Dumper;

my $conf_file = 'data.ini';
my $c = Config::Tiny->new->read($conf_file);

say Dumper($c);
say $c->{site1}->{title};

実行結果

$VAR1 = bless( {
                 'site2' => {
                              'url' => 'http://amari3.hatenablog.jp',
                              'author' => 'amari3',
                              'title' => 'amari3の日記'
                            },
                 'site1' => {
                              'url' => 'http://d.hatena.ne.jp/amari3',
                              'author' => 'amari3',
                              'title' => 'amari3のはてなダイアリー'
                            }
               }, 'Config::Tiny' );

amari3のはてなダイアリー

ini形式のサンプルでは、Config::Tinyオブジェクトになっているが、値へのアクセスはハッシュのリファレンスで普通にアクセスできる。

僕が考えるメリット
  • 設定ファイル自体がシンプルでわかりやすい
  • おそらく誰でも直感的に編集できる
  • 言語に基本的には依存しない
僕が考えるデメリット
  • セクションのネストができない
  • ↑により凝ったことをするのが難しい

yaml形式

有名なところでは、Pitで使われている形式。

設定ファイル例

% cat data.yaml
site1:
  url: http://d.hatena.ne.jp/amari3
  title: amari3のはてなダイアリー
  author: amari3

site2:
  url: http://amari3.hatenablog.jp
  title: amari3の日記
  author: amari3

設定ファイル読み込み例

use strict;
use warnings;
use feature qw( say );
use YAML::Syck;
use Data::Dumper;

my $conf_file = 'data.yaml';
my $c = YAML::Syck::LoadFile($conf_file);

say Dumper($c);
say $c->{site1}->{title};

実行結果

$VAR1 = {
          'site2' => {
                       'url' => 'http://amari3.hatenablog.jp',
                       'author' => 'amari3',
                       'title' => 'amari3の日記'
                     },
          'site1' => {
                       'url' => 'http://d.hatena.ne.jp/amari3',
                       'author' => 'amari3',
                       'title' => 'amari3のはてなダイアリー'
                     }
        };

amari3のはてなダイアリー

ハッシュリファレンスが返ってくるので、特に難しいところは無いと思う。

僕が考えるメリット
  • 配列とハッシュのネストができる
  • ↑により凝った設定ができる
  • 言語に基本的には依存しない
僕が考えるデメリット
  • ちゃんと使いこなそうとすると覚えることが多い

perl形式

設定ファイル自体もPerlで書いてしまう。

設定ファイル例

% cat data.perl
{
    site1 => {
        url    => 'http://d.hatena.ne.jp/amari3',
        title  => 'amari3のはてなダイアリー',
        author => 'amari3',
    },
    site2 => {
        url    => 'http://amari3.hatenablog.jp',
        title  => 'amari3の日記',
        author => 'amari3',
    },
}

設定ファイル読み込み例

use strict;
use warnings;
use feature qw( say );
use Data::Dumper;

my $conf_file = 'data.perl';
my $c = do $conf_file or die "$!$@";

say Dumper($c);
say $c->{site1}->{title};

実行結果

$VAR1 = {
          'site2' => {
                       'url' => 'http://amari3.hatenablog.jp',
                       'author' => 'amari3',
                       'title' => 'amari3の日記'
                     },
          'site1' => {
                       'url' => 'http://d.hatena.ne.jp/amari3',
                       'author' => 'amari3',
                       'title' => 'amari3のはてなダイアリー'
                     }
        };

amari3のはてなダイアリー

yaml形式と同様、ハッシュリファレンスなので、特に難しいところは無いと思う。

僕が考えるメリット
  • Perlのエンジニアなら凝った設定も簡単に書ける
  • Perlのコアの機能だけで実行できる
僕が考えるデメリット
  • perl形式なので当然他の言語には使えない
  • Perlのエンジニア以外には直感的で無い(と思う)

結局どれを使うの?

結論から言うと、プロダクトやプロジェクトに合ったものを選択すればいいと思う。と書くと何のためのエントリだと言う事になるので、僕の基準を書いてみようと思う。

  • Perlのみのプロダクト/プロジェクトで、エンジニアしか設定ファイルを変更しない場合はperl形式を使う
  • 他のシステムからも共通して使われる可能性がある場合は、ini/yaml形式を使う
  • 非技術者も設定ファイルを変更する可能性がある場合は、極力ini形式を使う

といったところでしょうか。当たり前のことを書いているけど、一度整理できたので良しとします。また、設定ファイルをプログラムから書き換えるといった要件が出てくれば、この限りではないです。