まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2006/07/26(水)FileHandle vs Symbol

perl でオブジェクト指向を目指し、use strictしたプログラムを徹底していくと、どうにかしたくなるのが「ファイルハンドル」の存在です。

open(FD, "test.txt");
close(FD);

この FD をオブジェクトとして使い関数に対して引数として与えたりしたいのですが、use strict な環境では

my $fh = 'FD';
open($fh, "test.txt");
close($fh);

とやっても、エラーになってしまいます。かと言ってこのためだけに no strict refs; ともしたくない。またこの方法では、Perlをマルチスレッド動作させるとき、ファイルディスクリプタの名前空間が衝突し、ファイルが開けなくなる問題もあります。

ネットで情報を漁っていると、こういうときはファイルハンドルを動的生成する方法が紹介されています。

use FileHandle ();
my $fh = FileHandle->new();
open($fh, "test.txt");
close($fh);
use IO::File ();
my $fh = IO::File->new();
open($fh, "test.txt");
close($fh);

いずれの方法も実際うまく動きますし、動作上はなんの問題もありません。ただ、これらのモジュールはファイルハンドルを生成するためだけに使うにはリッチすぎます。つまり機能的すぎてモジュールのロードが遅いということです。*1

たた単純に、ファイルハンドルを動的生成したい場合は、

use Symbol ();
my $fh = Symbol::gensym();
open($fh, "test.txt");
close($fh);

とするのがベストだと思われます。

どれくらい速度が違うか、それぞれのサンプルだけのプログラムを作成し実行し、time コマンドで計測してみましたので参考までに。

実現方法実行速度
FileHandle100ms
IO::File94ms
Symbol23ms
直接指定14ms

こういう計測の結果、adiary では Symbol モジュールを使っています。

*1 : 逆に言えば、Socket通信のハンドルを作り、出力を自動的にフラッシュしたいときなどは FileHandle や IO::File モジュールを用いて $fh->autoflash(1) などとするのがベターです。

Perl 5.6.0以降限定、もっとよい方法

Perl 5.6.0からの新機能として、ファイルハンドルを自動で生成する機能があります

ハンドラを使用する関数 (open(), opendir(), pipe(), socketpair(), sysopen(), socket(), accept()) では、ファイルハンドルとして未定義のスカラ変数が与えられたとき、ファイルやディレクトリのハンドルを自動的に生成し変数に設定します。

と書かれています(意訳)。つまりどういうことかというと、上と同等のことをやるためには、

open(my $fh, "test.txt");
close($fh);

と書けば十分だということになります。こうすれば Symbol モジュールのロードは必要なくなり、メモリや実行時間の節約になります。ただし、これが実行できるのは5.6.0以降ですので、ソースの最初に

use 5.6.0;

と書いておくべきでしょう。adiaryでは現在(β7以降)この方法を使用しています。