foreach と map を比べてみた

perlの話です。

文字列1と文字列2を比較して、文字列2に文字列1の文字が全て含まれているか確認する処理をperlで書いてる最中に、「foreachとmapのどちらで書いた方が早いのか」と急に気になったのでベンチマークして比較してみました。

つまりこんなような処理をしたときに・・・

my $count1 = 'abcdefghijklmnopqrstuvwxyz';
my $count2 = 'bzdfijlmonqrsvaehykcwgptux';

$count2 =~ s/$_// foreach( split("", $count1) ); # ここでmapを使うべきか?

上記foreachを使っている箇所をmapにした方が高速化が望めるのかを確認してみたくなったわけです。


こんな感じでBenchmarkモジュールを使って比較してみます。

#!/usr/local/bin/perl5.14.3

use strict;
use warnings;

use Benchmark qw(cmpthese);

my $count1 = 'abcdefghijklmnopqrstuvwxyz';
my $count2 = 'bzdfijlmonqrsvaehykcwgptux';

my $code = {
        'foreach'       =>      q|$count2 =~ s/$_// foreach(split("", $count1));|,
        'map'           =>      q|map { $count2 =~ s/$_//; } split("", $count1);|,
};

my $iterate = 10_000_000;

cmpthese($iterate, $code);


で、結果を確認してみたところ・・・

# ./bench_foreach.pl
             Rate foreach     map
foreach 2191781/s      --    -72%
map     7901235/s    260%      --


mapの方が相当早いようですね。差が開くのではないかとは思っていましたが、まさか250%近く差が出るとは思ってもみませんでした。foreachってもしかして今回のような書き方だとイテレーションごとにsplitを実行してるのかな・・・?
というわけで、ベンチマークするコードに「既にsplitで分離し終わったリストを使うforeach」を追加してテストしてみます。

my $count1 = 'abcdefghijklmnopqrstuvwxyz';
my $count2 = 'bzdfijlmonqrsvaehykcwgptux';
my @count3 = split("", $count1);

my $code = {
        'foreach'       =>      q|$count2 =~ s/$_// foreach(split("", $count1));|,
        'map'           =>      q|map { $count2 =~ s/$_//; } split("", $count1);|,
        'splitted'      =>      q|$count2 =~ s/$_// foreach(@count3);|,
};
# ./bench_foreach.pl
              Rate  foreach splitted      map
foreach  2180579/s       --     -59%     -73%
splitted 5267490/s     142%       --     -35%
map      8050314/s     269%      53%       --


うーん・・・foreachの中でsplitを実行しているコードよりは早くなったようですが、それでもmapには勝てないようです。速度を求めるならmapを使った方がよさそうですね。



折角なんでmapの方でも分離し終わったリストを使ってみましょう。

my $code = {
        'foreach'       =>      q|$count2 =~ s/$_// foreach(split("", $count1));|,
        'map'           =>      q|map { $count2 =~ s/$_//; } split("", $count1);|,
        'splitted'      =>      q|$count2 =~ s/$_// foreach(@count3);|,
        'splitted_map'  =>      q|map { $count2 =~ s/$_//; } @count3|,
};
# ./bench_foreach.pl
                   Rate      foreach     splitted          map splitted_map
foreach       2173175/s           --         -60%         -70%         -79%
splitted      5446809/s         151%           --         -25%         -47%
map           7272727/s         235%          34%           --         -30%
splitted_map 10322581/s         375%          90%          42%           --

やはりsplitted_mapの方が早いですね。30回程度スクリプトを実行してみましたが、大体20~150%程度の差が出るようです。
(修正: 2013/06/21 16:00 最後のベンチマーク結果がこの記事に記載されているものと別のスクリプトを実行したものだったので修正)

ffmpegのportsを0.9に対応してみる

ちょっと思うところがあってffmpegをサーバにインストールしようかとportsを探ってみたところ、どうやらmultimedia/ffmpegはバージョン0.7.8のまま放置されているのが解ったうえに、multimedia/ffmpeg-develは

[root@hogehoge /usr/ports/multimedia/ffmpeg-devel]# make
 ===>  ffmpeg-2011.10.09_1 is marked as broken: does not build.
 *** Error code 1

Stop in /usr/ports/multimedia/ffmpeg-devel.

brokenになっていたので、このまま0.9を待ってるのも面倒だからmultimedia/ffmpegをベースに0.9へと対応させてみることにしました。

……で、いつもならここに具体的な作業手順を書くところなのだけど、どうせなので登録したっきり放置してあったgithubを本格的に活用してみようかということで、作成したportをgithubに公開してみることにしました。
注:とりあえず既存のmultimedia/ffmpegを強引に0.9へ対応させただけなので、files/以下にあるパッチファイル群の細かいチェックとかは一切やってません。当方の環境では記事執筆時(2011/12/27)には特に問題が出ていませんが、ゆめゆめご注意を。

リポジトリはここに置いてあります。
https://github.com/wizard-blue/freebsdports-ffmpeg-0.9

で、インストールは以下の通り*1

# git clone git://github.com/wizard-blue/freebsdports-ffmpeg-0.9.git ffmpeg-0.9
# cd ffmpeg-0.9
# make install

いやーしかし便利ですねgithub。

*1:/usr/ports/multimedia以下で実行しなくてもインストール出来ます。各人が管理しやすい場所でどうぞ

SMTP AUTHチェックスクリプトを書いてみた

 ちょっとした理由で特定のメールサーバのSMTP AUTHが機能してるかを確認する必要が出てきたのでちょっと書いてみました。
 半年くらい前に書いて以来Evernoteに入れて満足していたのですが、自分だけで保存しておくのはちょっともったいないくらい簡単にできたのでここに書き残しておくことにします。

 さて、問題のスクリプトですが……実はSMTP AUTH検証はNet::SMTPを使えばちょー楽に書けます。

#!/usr/local/bin/perl

use Net::SMTP;
use strict;
use warnings;

my ($mailserver, $user, $password) = @ARGV;
my $smtp = Net::SMTP->new($mailserver, Debug=>1);

$smtp->auth($user, $password);
$smtp->quit;

exit 0;

↓実行結果↓

# ./smtpauthchk.pl smtp.example.com hogehoge@example hogehoge
Net::SMTP>>> Net::SMTP(2.31)
Net::SMTP>>>   Net::Cmd(2.29)
Net::SMTP>>>     Exporter(5.58)
Net::SMTP>>>   IO::Socket::INET(1.29)
Net::SMTP>>>     IO::Socket(1.29)
Net::SMTP>>>       IO::Handle(1.25)
Net::SMTP=GLOB(0x81a9cc4)<<< 220 example.com ESMTP
Net::SMTP=GLOB(0x81a9cc4)>>> EHLO localhost.localdomain
Net::SMTP=GLOB(0x81a9cc4)<<< 250-example.com
Net::SMTP=GLOB(0x81a9cc4)<<< 250-STARTTLS
Net::SMTP=GLOB(0x81a9cc4)<<< 250-PIPELINING
Net::SMTP=GLOB(0x81a9cc4)<<< 250-8BITMIME
Net::SMTP=GLOB(0x81a9cc4)<<< 250-SIZE 31457280
Net::SMTP=GLOB(0x81a9cc4)<<< 250 AUTH LOGIN PLAIN CRAM-MD5
Net::SMTP=GLOB(0x81a9cc4)>>> AUTH CRAM-MD5
Net::SMTP=GLOB(0x81a9cc4)<<< 334 PDkxMzczLjEyNjg3MjA5NDlAa21teDAwcy5hZG1pcmFsLm5lLmpwPg==
Net::SMTP=GLOB(0x81a9cc4)>>> YXJhaUBrbS5hc2oubmUuanAgYTY3OTM0ZDMxOTA5NDJlOWE3Y2QwMzFmNDcwM2Y0NDc=
Net::SMTP=GLOB(0x81a9cc4)<<< 235 ok, go ahead (#2.0.0)
Net::SMTP=GLOB(0x81a9cc4)>>> QUIT
Net::SMTP=GLOB(0x81a9cc4)<<< 221 example.com

 上記ではSMTP AUTHによる認証が成功しました。これ「小ネタ」でも良かったなー。

今日の小ネタ! (2) push()で配列を統合できる

 今日の小ネタ!
 「コレ多分出来るよね」って思ってやってみたら出来ちゃった!けど一発ネタすぎる!という内容を投げっぱなしで書いて終わるコーナーです。


 今回は「push()って解説サイトとかではスカラを継ぎ足す方法しか書いてないけどリストも継ぎ足せるんじゃ?」と思い立ってやってみました。

#!/usr/local/bin/perl

use strict;

my @list1 = qw(a b c);
my @list2 = qw(1 2 3);
my @list3;

push @list3, @list1, @list2;

print "result:[@list3]\n";

exit 0;

↓実行結果↓

# perl test.pl
result:[a b c 1 2 3]

 以上です!できました。リストをくっつけたい時もpush()を使ってよいようです。

今日の小ネタ!(1) 特殊変数$nはスコープの影響を受ける

 今日の小ネタ!
 コーディングしてて気がついたトリビア的な(知ってる人にはトリビアでもなんでもないけど)内容を唐突に書き殴って終わって行こうと思います!

#!/usr/local/bin/perl

use strict;

my $val;
my $matched1;
my $matched2;

$val = "hoge12345";
$val =~ /(\d+)$/;

if ($val) {
    $val =~ /^(hoge)/;
    $matched1 = $1;

print "matched1 [$matched1]\n";
}

$matched2 = $1;

print "matched2 [$matched2]\n";

exit 0;

↓実行結果↓

#perl test.pl
matched1 [hoge]
matched2 [12345]

 以上です!$nを使う場合には注意するとよいでしょう。

ここ最近の私がやらかした極めてみっともない失敗を晒す(1) open()の成否をif()で確認しようとした

 前々から「ブログでプログラムの話題に触れたらいつかはやっておかないといけないだろうなあと思っていた事をやってみようかと思い立って、ちょうどネタもあったし唐突にコーナーを始めてみることにしました。
 世の中に手本となる記事は数あれど、これから僕が書こうとしている記事は恐らくほとんどの人間にしてみれば全く役に立たないどころか害になる可能性を否定しきれないことを予めご報告しておきます。みんなは真似するなよ(未来の自分も含む)!

 今回は「open()の成否をif(<ファイルハンドル>)で確認しようとした」です。通常はその場でopenに成功したか確認するのが普通ですが、このときはちょっとした実験のつもりで下記のようなことをやってみました。

#!/usr/bin/perl

use strict;

open FILE, "./test.txt";

#### 間にいくつかの処理を挟む ####

if (<FILE>) {
    while (<FILE>) {
	chomp;
	print "$_\n";
    }
}

close FILE;'

exit 0;


上記のようなコードの場合、if ()の箇所で1行ファイルの中身が読まれてしまうため、実行結果に表示されるファイルの中身は2行めからになります。阿呆ですねこのコード書いた人!

正解は以下の通りです。

#!/usr/bin/perl

use strict;
use IO::File;

my $handle = new IO::File "./test.txt", "r";

#### 間にいくつかの処理を挟む ####

if (!defined $handle) {
    goto abort_func;
}

while (<$handle>) {
    chomp;
    print "$_\n";
}

undef $handle;

exit 0;


abort_func:

print "Error occured.\n";
exit 1;


手動でフラグを立てて力業で管理するよりは素直にモジュールに頼っちゃった方がごたつかなくてよいです。
というかそもそも「ファイルを開くのは書き込み|読み込みたい直前にする」という鉄則を守るべきでしょう。

こんなしょうもないミスかますとかほんとしょうもない。

結論: ファイルが開けたか後でチェックしたい場合はIO::Fileを使う

根本的な結論: そもそもファイルを開くタイミングは書き込み|読み込みたい直前にする

dovecotでIMAPの挙動を細かくログに取ってみる

 dovecotが1.2系になって大分経ちますね。1.2系からは以前から実装済みだったmail_logプラグインにイベントのログを取る機能が追加されまして、これの御陰でPOP3/IMAP共にメールの受信・削除・移動等々が細かくログに残せるようになりました。これ意外に知ってる人少ないんですよね……ググっても日本語の情報源が殆ど出て来ませんでした(2010/08/06現在)。探し方が悪かったのかも知れませんが……

 まあそんなわけで、IMAPのログを細かく取る方法を以下に書き残していきます。作業は簡単、dovecotの設定ファイル(当環境では/usr/local/etc/dovecot.conf)を以下のように編集します。そうそう、言い忘れてましたが今回設定した内容によって記録されるログは、通常のdovecotのログ出力先として設定されているところに一緒に書かれます。

 まずはIMAPから。

protocol imap{

(略)

  mail_plugins = mail_log
  mail_plugin_dir = /usr/local/lib/dovecot/imap

(略)

}

(略)

plugin {
  mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
}


 基本的にコメントアウトされていたもののコメントを外してmail_pluginsの項目にmail_logを追加するだけです。
 plugin{}の内部に記述したmail_log_eventsの項目についての具体的な解説はdovecot公式のドキュメントに詳しく書いてあります。っていうかここを見ればこの記事を見る意味は殆どなかったりします(苦笑) 文書が整ってるソフトは使いやすいなあ!(白目
 気を取り直して……この設定によって書かれるログは例えばこんな感じに。

hogehoge dovecot: IMAP(hogehoge): expunge: uid=xxxx, msgid=, from==Fromヘッダの内容, subject==Subjectヘッダの内容, size=メッセージのサイズ

 このログはexpunge……つまりあるフォルダからメールを完全に削除した記録ですね。IMAPの具体的な挙動についてはこの辺を読むと幸せになります。是非ご覧あれ。

 ちなみにpop3でも同様の記録が取れます。やり方はIMAPと殆ど同じで、

protocol pop3{

(略)

  mail_plugins = mail_log
  mail_plugin_dir = /usr/local/lib/dovecot/pop3

(略)

}

(略)

plugin {
  mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
}

 殆ど変わりませんね。protocol imap{}の中に書いていた記述をprotocol pop3{}の中に書いただけです。plugin{}の中の記述はIMAP/POP3で同じ項目を参照しますので、IMAPで設定した場合は改めて追記する必要はありません。
 この設定によって書かれるログは例えばこんな感じに。

hogehoge dovecot: POP3(hogehoge): expunge: uid=xxxx, msgid=, from==Fromヘッダの内容, subject==Subjectヘッダの内容, size=メッセージのサイズ


 IMAPと殆ど変わりませんね。このアカウントを受信したメーラーはメールをサーバに残さない設定になっているため、受信したメールを削除した……ということでexpungeとして記録されています。


 以上です。かなり簡単ですが、異様に便利なので細かく記録する必要がある場合には是非活用してください。それと一つ重要なことがありまして、ログの特性上容量が膨大になりがちですIMAPを使ってガリガリサーバを運営していきたい場合はログのローテーション等々に注意してください。