Encode モジュール

Pooh さんのベンチマーク を読んで,PHP ってそんなに文字変換速かったっけ?と思ったので軽くベンチマークをとってみました。
…えーっと,別に PHPPerl のどちらかを貶める意図はないです(むろん,Pooh さんも hippo2000 さんもそういう意図はないはずです)。単純に Perl の日本語変換で幸せになるにはどうすればいいんだろうという知的好奇心からやってみました。以下のステップをご覧いただくとわかるとおり,厳密なベンチマークというわけではないです。また,Benchmark モジュールを使わず双方 time() 関数による1秒単位の計測です。具体的なスクリプト等については載せないので追試可能性がなくなってしまってすいません。
環境は,

  • Perl 5.8.8 + Encode 2.15
  • PHP 5.1.2

で,いずれも CLI です。また,変換するファイルは郵便番号住所ファイル(ken_all.csv 約12万行,約12MB)です。
ファイルキャッシュ等の影響を排除するために,一度空読みして(あえてキャッシュさせ),まずは無変換で /dev/null に出力(統制条件),次に Shift_JISEUC-JP 変換して /dev/null に出力,という流れにしました。一行ごとに変換するという,Pooh さんのスタイルに近いプログラムです。
結果は,

PerlPHP
無変換1秒弱1秒強
変換約10秒約5秒
ということで,日本語変換自体について PHP のほうが Perl より約2倍速いという Pooh さんの結果に近い形となりました。たしかに速い。でも,ファイルから文字列行を読んでいくという部分は体感速度としても心持ち Perl のほうが速いです。


これで終わると Pooh さんの内容の劣化コピーになるので,原因を考えてみました。最初,Perl が一度内部コードを経由して変換しているのに対し,ひょっとして PHP は,Shift_JISEUC-JP にダイレクトに変換しているのかな,と思いました。が,libmbfl のソースを流し読みした感じだと,やはり一度 UCS-2 に変換している模様です。ということで逆に Encode.pm を眺めていたところ,Encode::from_to() では,呼び出される都度,Encode::find_encoding() することになっています。このへんが XS ではなくスクリプトなもので,Perl にとっては苦しいところなのかな,と思いました。なので,

while (<INPUT>) {
  Encode::from_to($_, 'shiftjis', 'euc-jp');
  print OUTPUT $_;
}

となっていたところを

my $ef = Encode::find_encoding('shiftjis');
my $et = Encode::find_encoding('euc-jp');

while (<INPUT>) {
  my $s = $ef->decode($_);
  $_ = $et->encode($s);
  print OUTPUT $_;
}

に変えてみました(ちょっとずるい?)。
その結果,

PerlPHP
無変換1秒弱1秒強
変換約5秒約5秒
ということで,PHP の速度にかなり近づきました。Encode.pm が libmbfl に比べて無駄な処理をしているわけではなかったようです。よかったよかった。
現実問題として,このスクリプトのように一行ずつ大量に変換していくということはあまりないと思うので,普段はより直感的な Encode::from_to() や Encode::decode() や Encode::encode() を使う方がいいと思います。文字コードを指定したファイル読み出しには Encode::PerlIO という手もありますしね。