標準ファイルハンドルとIO::Fileモジュールのベンチマーク

IO::Fileと普通にファイルハンドル使うのとどっちが速いのかなぁと思い、ベンチマークスクリプトを書いて試してみました。

Perlのバージョンは5.8.8です。

#!/usr/bin/perl

use strict;
use warnings;

use IO::File;
use Benchmark;

my $file = "hoge.txt";

timethese ( 100000,
            { 'IO::File',   '&with_IO_File;',
              'FileHandle', '&without_IO_File;',
            } );

sub with_IO_File {
    my $fh = IO::File->new($file);
    push my @lines, $_ while <$fh>;
    $fh->close;
    \@lines;
}

sub without_IO_File {
    open FH, $file;
    push my @lines, $_ while <FH>;
    close FH;
    \@lines;
}

結果は以下のようになりました。

Benchmark: timing 100000 iterations of FileHandle, IO::File...
FileHandle: 44 wallclock secs (32.47 usr +  9.25 sys = 41.72 CPU) @ 2396.93/s (n=100000)
  IO::File: 104 wallclock secs (92.71 usr +  9.91 sys = 102.62 CPU) @ 974.47/s (n=100000)


次に、ファイルハンドルから配列に格納する処理を取り除き、ファイルのオープン・クローズ部分だけの処理速度を比較してみました。

sub with_IO_File {
    my $fh = IO::File->new($file);
    $fh->close;
}

sub without_IO_File {
    open FH, $file;
    close FH;
}

結果は以下の通りでした。

Benchmark: timing 100000 iterations of FileHandle, IO::File...
FileHandle: 19 wallclock secs (13.19 usr +  6.22 sys = 19.41 CPU) @ 5151.98/s (n=100000)
  IO::File: 80 wallclock secs (73.56 usr +  5.74 sys = 79.30 CPU) @ 1261.03/s (n=100000)


処理時間の差分がほぼ一致することから、オープン・クローズの部分で処理速度に差があるようです。


・・・というか、ここでIO::Fileのソース読んでみて(←最初から嫁)気づいたんですが、IO::Fileのopenやclose(IO::Handleのclose)って内部で標準のopenやclose呼んでるだけですね。。

そりゃ標準のファイルハンドル使った方が速いに決まってるという・・・


という事で一段落、、と思いきや、ついでにIO::Fileのオブジェクト生成とファイルオープンを分離して試してみたところ、

#    my $fh = IO::File->new($file);
    my $fh = IO::File->new();
    $fh->open($file);

僅かにパフォーマンスが向上したのですが、

Benchmark: timing 100000 iterations of FileHandle, IO::File...
FileHandle: 41 wallclock secs (32.18 usr +  9.86 sys = 42.04 CPU) @ 2378.69/s (n=100000)
  IO::File: 94 wallclock secs (87.75 usr +  7.47 sys = 95.22 CPU) @ 1050.20/s (n=100000)


この様にopenに第二引数で'r'(読み取りモード)のオプションを与えてみたところ、

#    my $fh = IO::File->new($file);
    my $fh = IO::File->new();
    $fh->open($file, 'r');

オプション無と比較して30倍足らずの処理時間を要するようになり、大幅にパフォーマンスが低下してしまいました。

Benchmark: timing 100000 iterations of FileHandle, IO::File...
FileHandle: 44 wallclock secs (31.44 usr + 12.64 sys = 44.08 CPU) @ 2268.60/s (n=100000)
  IO::File: 1121 wallclock secs (249.46 usr 141.65 sys + 180.28 cusr 543.41 csys = 1114.80 CPU) @ 255.68/s (n=100000)


実行プロセスが肥大化しているとともに、内部でプロセスをコールするようで、この子プロセスもまたかなりCPUを食っているようです。

再びIO::Fileのソースを読んでみると、ここのif文判定でFile::Spec->file_name_is_absolute($file)を呼んでいて、これがコールされた子プロセスになります。

   if (@_ > 2) {
        my ($mode, $perms) = @_[2, 3];

        --- (中略) ---

        if (defined($file) && length($file)
            && ! File::Spec->file_name_is_absolute($file))
        {
            $file = File::Spec->rel2abs($file);
        }
        $file = IO::Handle::_open_mode_string($mode) . " $file\0";
    }

この判定処理は、IO::File->open()では引数の個数が2より大の場合のみ通過するので、モードを指定して引数が3個になった途端にパフォーマンスに影響が出てしまっているようです。


Perlモジュールを使ったり、自分で書いていく上で(性能面で)こういうところにも気を配る必要があるんだな」と思いがけず勉強になりました。