インデックスを使った指定行取り出しプログラム(Pure Perl)
2010-08-10-1
[Programming][Algorithm]
テキストファイルから指定した行を取り出すタスクについて。
まずはファイルの頭から走査していくナイーブな方法。
■コード(getline-naive.pl):
先頭行は0行目になる仕様。
■実行例:
頭から走査する方法はファイルサイズが巨大になると破綻するのでインデックスを使う。
まずインデックスを作成するプログラム。
■コード(mkidx.pl):
■実行例:
各行の先頭バイト位置を固定長バイトにして出力。
SUFARY の "mkary -l -ns" と同じ出力になる。
というか、互換にしてみた。
N行目(0始まり)の開始バイト位置は「N * sizeof(unsigned long)」個目に格納されている。
なので、あとは seek と read で行を取り出せばOK。
ゆえに頭から見ていく前述の方法よりも高速になる。
ということで、インデックスを使った指定行取り出しプログラム。
■コード(getline.pl):
■実行例:
実際に取り出し速度を測定して比較してみた。
使ったデータは60Mバイト3万行のテキストファイル。
指定した200行を取り出すタスク(プログラムを200回起動する)。
それぞれの実行時間を測定した。
結果:インデックスを使う手法の方が頭から走査する手法よりも10倍高速であった。
- なぜ「$len_of_N = 4」なのかというと、pack の N は 32ビット(4バイト)ゆえ。"perldoc -f pack" に書いてある。(追記100811)
- [を] 文字列の ID 化と相互変換を SUFARY を使って行う方法[2008-04-10-2]
- インデックスを使った指定行取り出しプログラム(Pure Ruby) - Maeの(Mae向きな)日記
http://d.hatena.ne.jp/rahaema/20100811#p1
頭から走査する方法
まずはファイルの頭から走査していくナイーブな方法。
■コード(getline-naive.pl):
#!/usr/bin/perl
use strict;
use warnings;
my $qidx = shift @ARGV; # start from 0
my $tgtfn = shift @ARGV;
open(my $fh, "<", $tgtfn) or die;
while (<$fh>) {
next if $. != $qidx + 1;
print;
last;
}
close $fh;
先頭行は0行目になる仕様。
■実行例:
% cat test.dic Japan Tokyo Yokohama This is a pen Hello World % ./getline-naive.pl 2 test.dic Yokohama
インデックスを使う方法
頭から走査する方法はファイルサイズが巨大になると破綻するのでインデックスを使う。
まずインデックスを作成するプログラム。
■コード(mkidx.pl):
#!/usr/bin/perl
use strict;
use warnings;
my $ip = 0;
while (<>) {
print pack("N", $ip);
$ip += length($_);
}
■実行例:
% ./mkidx.pl test.dic > test.dic.ary % od -t x1 test.dic.ary 0000000 00 00 00 00 00 00 00 06 00 00 00 0c 00 00 00 15 0000020 00 00 00 23 0000024
各行の先頭バイト位置を固定長バイトにして出力。
SUFARY の "mkary -l -ns" と同じ出力になる。
というか、互換にしてみた。
N行目(0始まり)の開始バイト位置は「N * sizeof(unsigned long)」個目に格納されている。
なので、あとは seek と read で行を取り出せばOK。
ゆえに頭から見ていく前述の方法よりも高速になる。
ということで、インデックスを使った指定行取り出しプログラム。
■コード(getline.pl):
#!/usr/bin/perl
use strict;
use warnings;
my $len_of_N = 4;
my $qidx = shift @ARGV; # start from 0
my $tgtfn = shift @ARGV;
my $idxfn = shift @ARGV || $tgtfn.".ary";
my $tfsz = -s $tgtfn;
my $ifsz = -s $idxfn;
die if $qidx < 0 or $qidx * $len_of_N >= $ifsz;
my $buf = "";
open(my $fi, "<", $idxfn) or die;
seek $fi, $qidx * $len_of_N, 0;
read $fi, $buf, $len_of_N;
my $ixf = unpack("N", $buf);
my $ixt = (tell $fi < $ifsz) ? do {
read $fi, $buf, $len_of_N;
unpack("N", $buf);
} : $tfsz;
close $fi;
open(my $fh, "<", $tgtfn) or die;
seek $fh, $ixf, 0;
read $fh, $buf, $ixt - $ixf;
close $fh;
print $buf;
■実行例:
% ./getline.pl 2 test.dic Yokohama
速度比較
実際に取り出し速度を測定して比較してみた。
使ったデータは60Mバイト3万行のテキストファイル。
指定した200行を取り出すタスク(プログラムを200回起動する)。
それぞれの実行時間を測定した。
結果:インデックスを使う手法の方が頭から走査する手法よりも10倍高速であった。
補足
- なぜ「$len_of_N = 4」なのかというと、pack の N は 32ビット(4バイト)ゆえ。"perldoc -f pack" に書いてある。(追記100811)
関連記事
- [を] 文字列の ID 化と相互変換を SUFARY を使って行う方法[2008-04-10-2]
- インデックスを使った指定行取り出しプログラム(Pure Ruby) - Maeの(Mae向きな)日記
http://d.hatena.ne.jp/rahaema/20100811#p1
