perlでgzipファイルを最速で読み込む方法

いくつか方法があるが、それぞれ試してみる。

条件

  1. open 非圧縮ファイル読み込み
  2. open zcat(gzip -cd) 標準出力読み込み
  3. IO::File zcat(gzip -cd) 標準出力読み込み
  4. Compress::Zlib

サンプルファイル

内容
CSVファイル
行数
6309行
sample.csv
1661636 bytes (1.6M bytes)
sample.csv.gz
196774 bytes (193K bytes)

環境

OS
Debian GNU/Linux 4.0 (coLinux 0.7.1)
Perl
v5.8.8
メモリ
512MB/2048MB (coLinux割り当て/全体)
PC
HP ProLiant ML115 (Windows XP)

計測用コードとか

一応測ったけどgzipにおまけでついてくるzcatは、gzip -cd と同じ性能。
そもそもバイナリも同じサイズ。
読み込みに使うファイルは上記の通り。最近はTSVしか使わないけど、手元にCSVがあったので流用。
結局あまりにでかいファイルは、普通は分割して処理するだろうしこのぐらいが無難じゃないかと。
100回ぐらいじゃ値が定まらないので1000回実行。

bench_gzip.pl

#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw( :all );
use Compress::Zlib;
use IO::File;

my $file       = 'sample.csv.gz';
my $file_plain = 'sample.csv';

print "warming up the file...\n";
system( "zcat $file > /dev/null" );
system( "gzip -cd $file > /dev/null" );

print "starting comparison...\n";

cmpthese( 1000, {
    'uncompressed'  => \&uncompressed,
    'open_zcat'     => \&open_zcat,
    'fileio_zcat'   => \&fileio_zcat,
    'open_gzip'     => \&open_gzip,
    'fileio_gzip'   => \&fileio_gzip,
    'compress_zlib' => \&compress_zlib,
});
print "\n";

sub uncompressed {
    open my $fh, '<', $file_plain
        or die "Can't open '$file_plain': $!";
    my $lines = 0;
    while ( my $line = <$fh> ) {
        $lines++;
    }
    close $fh
        or die "Can't close '$file_plain' after reading: $!";
    print "plain_open: $lines lines\n";
}

sub open_zcat {
    open my $fh, "zcat $file 2>/dev/null |"
        or die "Can't zcat '$file' for reading: $!";
    my $lines = 0;
    while ( my $line = <$fh> ) {
        $lines++;
    }
    close $fh
        or die "Can't close '$file_plain' after reading: $!";
    print "open_zcat: $lines lines\n";
}

sub fileio_zcat {
    my $fh = IO::File->new( "zcat $file 2>/dev/null |" )
        or die "Can't zcat '$file' for reading: $!";
    my $lines = 0;
    while ( defined(my $line = $fh->getline()) ) {
        $lines++;
    }
    $fh->close
        or die "Can't close '$file' after reading: $!";
    print "fileio_zcat: $lines lines\n";
}

sub open_gzip {
    open my $fh, "gzip -cd $file 2>/dev/null |"
        or die "Can't gzip -cd '$file' for reading: $!";
    my $lines = 0;
    while ( my $line = <$fh> ) {
        $lines++;
    }
    close $fh
        or die "Can't close '$file_plain' after reading: $!";
    print "open_gzip: $lines lines\n";
}

sub fileio_gzip {
    my $fh = IO::File->new( "gzip -cd $file 2>/dev/null |" )
        or die "Can't gzip -cd '$file' for reading: $!";
    my $lines = 0;
    while ( defined(my $line = $fh->getline()) ) {
        $lines++;
    }
    $fh->close
        or die "Can't close '$file' after reading: $!";
    print "fileio_gzip: $lines lines\n";
}

sub compress_zlib {
    my $gz = gzopen( $file, 'rb' )
        or die "Cant't gzopen '$file' for reading: $!";
    my $line;
    my $lines = 0;
    while ( ( my $bytes = $gz->gzreadline( $line ) ) > 0 ) {
        die( $gz->gzerror ) if ( $bytes == -1 );
        $lines++;
    }
    $gz->gzclose();
    print "compress_zlib: $lines lines\n";
}

結果

Rate compress_zlib fileio_zcat fileio_gzip open_zcat open_gzip uncompressed
compress_zlib 5.40/s -- -84% -85% -91% -91% -95%
fileio_zcat 33.9/s 527% -- -5% -41% -41% -66%
fileio_gzip 35.8/s 562% 6% -- -38% -38% -64%
open_zcat 57.3/s 961% 69% 60% -- -0% -43%
open_gzip 57.3/s 961% 69% 60% 0% -- -43%
uncompressed 100/s 1752% 195% 180% 75% 75% --

非圧縮はこのぐらいのファイルサイズの差なら一番速い。
大きくなるにつれてたぶん差が縮まる。I/Oの差で圧縮してある方が速いこともあるかも。
Compress::Zlibは遅すぎ、ちょっとお話にならない。
IO::File使うと、openで処理するのと比べると倍ぐらい遅い。

ということで結論。普通にzcat(gzip -cd)の標準出力をパイプでopenするのが一番速い。
あっ!その場合、closeはいらないかも。