file1.txt
とfile2.txt
の2つのファイルがあります。 file1.txt
には約14K行あり、file2.txt
には約20億行あります。 file1.txt
には1行に1つのフィールドf1
がありますが、file2.txt
にはf1
からf3
までの3つのフィールドがあり、|
で区切られています。
file2.txt
of f1
of file1.txt
of f2
of file2.txt
からすべての行を検索したいfile2.txt
)の値を分割する余分な時間。
file1.txt(約14K行、ソートされていない):
foo1
foo2
...
bar1
bar2
...
file2.txt(約20億行、ソートされていない):
date1|foo1|number1
date2|foo2|number2
...
date1|bar1|number1
date2|bar2|number2
...
予想される出力:
date1|foo1|number1
date2|foo2|number2
...
date1|bar1|number1
date2|bar2|number2
...
これが私が試したものであり、実行するのに数時間かかるようです:
fgrep -F -f file1.txt file2.txt > file.matched
一般的なUnixコマンドまたは小さなスクリプトを使用してこの操作を行うより良い、より高速な方法があるかどうか疑問に思います。
Perlコードの小さな断片が問題を解決しました。これは取られたアプローチです:
file1.txt
の行をハッシュに保存するfile2.txt
を1行ずつ読み取り、2番目のフィールドを解析して抽出するこれがコードです:
#!/usr/bin/Perl -w
use strict;
if (scalar(@ARGV) != 2) {
printf STDERR "Usage: fgrep.pl smallfile bigfile\n";
exit(2);
}
my ($small_file, $big_file) = ($ARGV[0], $ARGV[1]);
my ($small_fp, $big_fp, %small_hash, $field);
open($small_fp, "<", $small_file) || die "Can't open $small_file: " . $!;
open($big_fp, "<", $big_file) || die "Can't open $big_file: " . $!;
# store contents of small file in a hash
while (<$small_fp>) {
chomp;
$small_hash{$_} = undef;
}
close($small_fp);
# loop through big file and find matches
while (<$big_fp>) {
# no need for chomp
$field = (split(/\|/, $_))[1];
if (defined($field) && exists($small_hash{$field})) {
printf("%s", $_);
}
}
close($big_fp);
exit(0);
上記のスクリプトを実行しましたが、file1.txtに14K行、file2.txtに1.3M行ありました。約13秒で終了し、126Kの試合が行われました。これは同じもののtime
出力です:
real 0m11.694s
user 0m11.507s
sys 0m0.174s
@Inianのawk
コードを実行しました:
awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (match($0,i)) {print; break}}' file1.txt FS='|' file2.txt
これは、file2.txtの各行で14K回ループするため、Perlソリューションよりもはるかに低速でした。 file2.txt
の592Kレコードを処理し、40K一致する行を生成した後、異常終了しました。所要時間は次のとおりです。
awk: illegal primary in regular expression 24/Nov/2016||592989 at 592989
input record number 675280, file file2.txt
source line number 1
real 55m5.539s
user 54m53.080s
sys 0m5.095s
@Inianの他のawk
ソリューションを使用すると、ループの問題が解消されます。
time awk -F '|' 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt > awk1.out
real 0m39.966s
user 0m37.916s
sys 0m0.743s
time LC_ALL=C awk -F '|' 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt > awk.out
real 0m41.057s
user 0m38.475s
sys 0m0.904s
awk
は、プログラム全体を記述する必要がなかったことを考えると、非常に印象的です。
@olivのPythonコードも実行しました。ジョブが完了するまでに約15時間かかりました。適切な結果が得られたようです。巨大な正規表現を作成することは、ハッシュを使用するほど効率的ではありません。ここでtime
出力:
real 895m14.862s
user 806m59.219s
sys 1m12.147s
私は parallel を使用するという提案に従いました。ただし、ブロックサイズが非常に小さい場合でも、fgrep: memory exhausted
エラーで失敗しました。
私を驚かせたのは、fgrep
がこれにはまったく不向きだったことです。 22時間後に中止し、約10万回の試合が行われました。 fgrep
に、Perlコードと同じように-f file
のコンテンツをハッシュに保持するオプションがあったらいいのにと思います。
私はjoin
アプローチをチェックしませんでした-ファイルをソートするための追加のオーバーヘッドが欲しくありませんでした。また、fgrep
のパフォーマンスが低いことを考えると、join
がPerlコードよりも優れていたとは思えません。
ご静聴ありがとうございました。
Perlソリューション。 [以下の注を参照してください。]
最初のファイルにはハッシュを使用します。大きなファイルを1行ずつ読み取るときに、正規表現(_||
_間の最初のパターンをキャプチャする)またはsplit
(2番目のWordを取得する)によってフィールドを抽出し、それがexists
。それらはおそらく速度が少し異なります(時間を計ります)。正規表現ではdefined
チェックは必要ありませんが、split
の場合は、短絡する_//
_(defined-or)を使用します。
_use warnings;
use strict;
# If 'prog smallfile bigfile' is the preferred use
die "Usage: $0 smallfile bigfile\n" if @ARGV != 2;
my ($smallfile, $bigfile) = @ARGV;
open my $fh, '<', $smallfile or die "Can't open $smallfile: $!";
my %Word = map { chomp; $_ => 1 } <$fh>;
open $fh, '<', $bigfile or die "Can't open $bigfile: $!";
while (<$fh>)
{
exists $Word{ (/\|([^|]+)/)[0] } && print;
# Or
#exists $Word{ (split /\|/)[1] // '' } && print;
}
close $fh;
_
if
ブランチを回避して短絡を使用する方が高速ですが、ごくわずかです。何十億もの行で、これらの微調整は追加されますが、多すぎません。上記のリストコンテキストではなく、小さいファイルを1行ずつ読み取る方が少し高速かもしれません(またはそうでないかもしれません)。ただし、これは気にならないはずです。
UpdateSTDOUT
への書き込みは2つの操作を節約し、ファイルに書き込むよりも少し速くなるように繰り返し時間を計測します。このような使用法は、ほとんどのUNIXツールでも一貫しているため、STDOUT
に書き込むように変更しました。次に、exists
テストは必要ありません。テストを削除すると、操作が不要になります。しかし、私は一貫してそれを使って少し良いランタイムを取得しますと同時に、目的をよりよく伝えます。まとめておきます。コメントを寄せてくれた ikegami に感謝します。
注コメントアウトされたバージョンは、他のバージョンより約50%高速です、以下の私のベンチマークで。これらは両方ともdifferentであるので、一方が最初の一致を見つけ、もう一方が2番目のフィールドを見つけるので、両方が与えられます。質問が曖昧であるため、私はこの方法をより一般的な選択として保持しています。
一部の比較(ベンチマーク)[STDOUT
への書き込み用に更新。上記の「更新」を参照]
HåkonHæglandによる回答 には広範な分析があり、ほとんどのソリューションの1回の実行のタイミングを計っています。上記の2つのソリューション、OP自体の回答、および投稿されたfgrep
のベンチマークを比較した別のテイクは、高速で、質問や多くの回答で使用されることが期待されています。
以下の方法でテストデータを作成します。大体示されている長さの一握りの行は、2番目のフィールドで一致するように、両方のファイルに対してランダムな単語で作成されます。次に、データサンプルのこの「シード」に一致しない行を埋め込むため、OPで引用されたサイズと一致の比率を模倣します。小さなファイルの14K行には、大きなファイルの1.3M行、126K一致次に、これらのサンプルを繰り返し書き込み、完全なデータファイルをOPとしてビルドします。毎回 List :: Util を使用してshuffle
- edします。
以下で比較したすべての実行は、上記のファイルサイズ(チェックにはdiff
- ed)に一致する_106_120
_を生成するため、一致頻度は十分に近くなります。それらは、my $res = timethese(60 ...)
を使用して完全なプログラムを呼び出すことによりベンチマークされます。 v5.16でのcmpthese($res)
の結果は
正規表現cfor分割fgrep 正規表現1.05/s--23%-35%-44% cfor 1.36/s 30%--16%-28% 分割1.62/s 54%19%--14% fgrep 1.89/s 80%39%17%-
最適化されたCプログラムfgrep
が上位にあることは驚くに値しません。 「regex」の「split」に対する遅れは、ほとんど一致しない場合にエンジンを起動するオーバーヘッドが原因である可能性があります(many回。進化する正規表現エンジンの最適化を考えると、これはPerlのバージョンによって異なる場合があります。 @codeforester( "cfor")の回答が含まれています。これは、最速であると主張されており、その_20%
_は非常に類似した "split "は、散在する小さな非効率性が原因である可能性があります(この回答の下のコメントを参照してください)。†
ハードウェアとソフトウェア、そしてデータの詳細には確かにばらつきがありますが、これは大きな違いはありません。私はこれをさまざまなPerlとマシンで実行しましたが、顕著な違いは、場合によってはfgrep
の方が桁違いに速いことです。
OPの非常に遅いfgrep
の経験は意外です。それらの引用された実行時間を考えると、上記よりも桁違いに遅いため、古いシステムには「責任がある」と思います。
これは完全にI/Oベースですが、複数のコアに配置することによる同時実行性の利点があり、数倍までの高速化が期待されます。
† 残念ながら、コメントは削除されました(?)。つまり、if
の代わりに、スカラー(コスト)、defined
ブランチ、printf
、print
の不要なスカラー(コスト)の使用(遅い!)。これらは20億回線の効率にとって重要です。
少しスピードアップできるAwk
を試してみましたか?
awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (match($0,i)) {print; break}}' file1.txt FS='|' file2.txt
(または) Benjamin W。 からのコメントで示唆されているように、Awk
でindex()
関数を使用する
awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (index($0,i)) {print; break}}' file1.txt FS='|' file2.txt
(または)コメントの Ed Morton で提案されている、より直接的な正規表現一致、
awk 'FNR==NR{hash[$1]; next}{for (i in hash) if ($0~i) {print; break}}' file1.txt FS='|' file2.txt
は、あなたが必要とすることすべてです。 100万以上のエントリを持つファイルでは、これはより高速になりますが正確にはわかりません。ここでの問題は、ライン上のどこにでも一致する可能性があることです。同じことが特定の列にあった場合(例:$2
のみ)、より高速なアプローチが可能です。
awk 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt
また、システムのlocale
セットで遊んで、速度を上げることもできます。この素晴らしい StéphaneChazelasの答え から言い換えると、ロケールLC_ALL=C
をコマンドlocally実行されています。
GNU
ベースのシステムでは、locale
のデフォルト
$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
1つの変数LC_ALL
を使用すると、すべてのLC_
タイプの変数を指定したロケールに一度に設定できます
$ LC_ALL=C locale
LANG=en_US.UTF-8
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=C
それで、これは何に影響しますか?
簡単に言うと、locale C
を使用すると、デフォルトでサーバーのベースUnix/Linux言語であるASCII
になります。基本的に、何かをgrep
すると、デフォルトでロケールが国際化され、UTF-8
に設定されます。これは、Unicode文字セットのすべての文字を表すことができ、現在、世界中の書記体系を表示するのに役立ちます。 110,000
を超える一意の文字を超えるのに対し、ASCII
では、各文字は1バイトシーケンスでエンコードされ、その文字セットは128
を超えない一意の文字で構成されます。
したがって、これに変換されます。UTF-8
文字セットでエンコードされたファイルでgrep
を使用する場合、各文字を10万の一意の文字のいずれかに一致させる必要がありますが、128
ASCII
にあるので、fgrep
を
LC_ALL=C fgrep -F -f file1.txt file2.txt
また、Awk
にmatch($0,i)
呼び出しでregex
一致を使用するため、C
ロケールを設定すると、文字列の一致。
LC_ALL=C awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (match($0,i)) {print; break}}' file1.txt FS='|' file2.txt
前提条件:1.この検索をローカルワークステーションだけで実行したいとします。 2.並列検索を利用するために複数のコア/ CPUがある。
parallel --pipepart -a file2.txt --block 10M fgrep -F -f file1.txt
コンテキストに応じてさらにいくつかの微調整:A. LANG = CでNLSを無効にします(これは別の回答ですでに説明されています)B. -mフラグで一致の最大数を設定します。
注:file2は〜4GBで、10Mのブロックサイズは問題ないと思いますが、実行を最速にするためにブロックサイズを最適化する必要がある場合があります。
_Inline::C
_ を使用して大きなファイル内の一致するフィールドの検索を高速化するPerlソリューションを次に示します。
_use strict;
use warnings;
use Inline C => './search.c';
my $smallfile = 'file1.txt';
my $bigfile = 'file2.txt';
open my $fh, '<', $smallfile or die "Can't open $smallfile: $!";
my %Word = map { chomp; $_ => 1 } <$fh>;
search( $bigfile, \%Word );
_
search()
サブルーチンは、 perlapi
を使用して純粋なCで実装され、小さなファイルディクショナリ_%words
_でキーを検索します。
search.c:
_#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define BLOCK_SIZE 8192 /* how much to read from file each time */
static char read_buf[BLOCK_SIZE + 1];
/* reads a block from file, returns -1 on error, 0 on EOF,
else returns chars read, pointer to buf, and pointer to end of buf */
size_t read_block( int fd, char **ret_buf, char **end_buf ) {
int ret;
char *buf = read_buf;
size_t len = BLOCK_SIZE;
while (len != 0 && (ret = read(fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror( "read" );
return ret;
}
len -= ret;
buf += ret;
}
*end_buf = buf;
*ret_buf = read_buf;
return (size_t) (*end_buf - *ret_buf);
}
/* updates the line buffer with the char pointed to by cur,
also updates cur
*/
int update_line_buffer( char **cur, char **line, size_t *llen, size_t max_line_len ) {
if ( *llen > max_line_len ) {
fprintf( stderr, "Too long line. Maximimum allowed line length is %ld\n",
max_line_len );
return 0;
}
**line = **cur;
(*line)++;
(*llen)++;
(*cur)++;
return 1;
}
/* search for first pipe on a line (or next line if this is empty),
assume line ptr points to beginning of line buffer.
return 1 on success
Return 0 if pipe could not be found for some reason, or if
line buffer length was exceeded */
int search_field_start(
int fd, char **cur, char **end_buf, char **line, size_t *llen, size_t max_line_len
) {
char *line_start = *line;
while (1) {
if ( *cur >= *end_buf ) {
size_t res = read_block( fd, cur, end_buf );
if (res <= 0) return 0;
}
if ( **cur == '|' ) break;
/* Currently we just ignore malformed lines ( lines that do not have a pipe,
and empty lines in the input */
if ( **cur == '\n' ) {
*line = line_start;
*llen = 0;
(*cur)++;
}
else {
if (! update_line_buffer( cur, line, llen, max_line_len ) ) return 0;
}
}
return 1;
}
/* assume cur points at starting pipe of field
return -1 on read error,
return 0 if field len was too large for buffer or line buffer length exceed,
else return 1
and field, and length of field
*/
int copy_field(
int fd, char **cur, char **end_buf, char *field,
size_t *flen, char **line, size_t *llen, size_t max_field_len, size_t max_line_len
) {
*flen = 0;
while( 1 ) {
if (! update_line_buffer( cur, line, llen, max_line_len ) ) return 0;
if ( *cur >= *end_buf ) {
size_t res = read_block( fd, cur, end_buf );
if (res <= 0) return -1;
}
if ( **cur == '|' ) break;
if ( *flen > max_field_len ) {
printf( "Field width too large. Maximum allowed field width: %ld\n",
max_field_len );
return 0;
}
*field++ = **cur;
(*flen)++;
}
/* It is really not necessary to null-terminate the field
since we return length of field and also field could
contain internal null characters as well
*/
//*field = '\0';
return 1;
}
/* search to beginning of next line,
return 0 on error,
else return 1 */
int search_eol(
int fd, char **cur, char **end_buf, char **line, size_t *llen, size_t max_line_len)
{
while (1) {
if ( *cur >= *end_buf ) {
size_t res = read_block( fd, cur, end_buf );
if (res <= 0) return 0;
}
if ( !update_line_buffer( cur, line, llen, max_line_len ) ) return 0;
if ( *(*cur-1) == '\n' ) {
break;
}
}
//**line = '\0'; // not necessary
return 1;
}
#define MAX_FIELD_LEN 80 /* max number of characters allowed in a field */
#define MAX_LINE_LEN 80 /* max number of characters allowed on a line */
/*
Get next field ( i.e. field #2 on a line). Fields are
separated by pipes '|' in the input file.
Also get the line of the field.
Return 0 on error,
on success: Move internal pointer to beginning of next line
return 1 and the field.
*/
size_t get_field_and_line_fast(
int fd, char *field, size_t *flen, char *line, size_t *llen
) {
static char *cur = NULL;
static char *end_buf = NULL;
size_t res;
if (cur == NULL) {
res = read_block( fd, &cur, &end_buf );
if ( res <= 0 ) return 0;
}
*llen = 0;
if ( !search_field_start( fd, &cur, &end_buf, &line, llen, MAX_LINE_LEN )) return 0;
if ( (res = copy_field(
fd, &cur, &end_buf, field, flen, &line, llen, MAX_FIELD_LEN, MAX_LINE_LEN
) ) <= 0)
return 0;
if ( !search_eol( fd, &cur, &end_buf, &line, llen, MAX_LINE_LEN ) ) return 0;
return 1;
}
void search( char *filename, SV *href)
{
if( !SvROK( href ) || ( SvTYPE( SvRV( href ) ) != SVt_PVHV ) ) {
croak( "Not a hash reference" );
}
int fd = open (filename, O_RDONLY);
if (fd == -1) {
croak( "Could not open file '%s'", filename );
}
char field[MAX_FIELD_LEN+1];
char line[MAX_LINE_LEN+1];
size_t flen, llen;
HV *hash = (HV *)SvRV( href );
while ( get_field_and_line_fast( fd, field, &flen, line, &llen ) ) {
if( hv_exists( hash, field, flen ) )
fwrite( line, sizeof(char), llen, stdout);
}
if (close(fd) == -1)
croak( "Close failed" );
}
_
テストは、ここに示した最速の純粋なPerlソリューション(私の other answer のメソッド_zdim2
_を参照)よりも約3倍速いことを示しています。
次のPerlスクリプト(a
)は、正規表現パターンを生成します。
#!/usr/bin/Perl
use strict;
use warnings;
use Regexp::Assemble qw( );
chomp( my @ids = <> );
my $ra = Regexp::Assemble->new();
$ra->add(quotemeta($_)) for @ids;
print("^[^|]*\\|(?:" . (re::regexp_pattern($ra->re()))[0] . ")\\|");
使用方法は次のとおりです。
$ LC_ALL=C grep -P "$( a file1.txt )" file2.txt
date1|foo1|number1
date2|foo2|number2
date1|bar1|number1
date2|bar2|number2
スクリプトはRegexp :: Assembleを使用しているため、インストールが必要になる場合があることに注意してください。
Sudo su
cpan Regexp::Assemble
ノート:
BOC1、BOC2、codeforester_orig、gregory1、inian2、inian4、olivと呼ばれるソリューションとは異なり、私のソリューションは正しく処理します
file1.txt
foo1
file2.txt
date1|foo12|number5
パターンはバックトラッキングを減らすように最適化されているため、鉱山は@BOCによる同様の solution よりも優れているはずです。 (リンクされたソリューションが失敗する可能性があるのに対し、file2.txt
に4つ以上のフィールドがある場合、鉱山は機能します。)
Split + dictionaryソリューションと比較する方法はわかりません。
ここにPythonソリューションを使用したソリューションがあります-概念的にはPerlキーのみのハッシュまたはawk配列とほぼ同等です。
#!/usr/bin/python
import sys
with open(sys.argv[1]) as f:
tgt={e.rstrip() for e in f}
with open(sys.argv[2]) as f:
for line in f:
cells=line.split("|")
if cells[1] in tgt:
print line.rstrip()
これを同じサイズのファイルで実行すると、約8秒で実行されます。
同じ速度:
$ awk 'FNR==NR{arr[$1]; next} $2 in arr{print $0}' FS="|" /tmp/f1 /tmp/f2
Pythonとここのawkソリューションは両方とも完全な文字列の一致のみであり、部分的な正規表現スタイルの一致ではありません。
Awkソリューションは高速でPOSIXに準拠しているので、それがより良い答えです。
join
を試していただけますか?ただし、ファイルはソートする必要があります...
$ cat d.txt
bar1
bar2
foo1
foo2
$ cat e.txt
date1|bar1|number1
date2|bar2|number2
date3|bar3|number3
date1|foo1|number1
date2|foo2|number2
date3|foo3|number3
$ join --nocheck-order -11 -22 -t'|' -o 2.1 2.2 2.3 d.txt e.txt
date1|bar1|number1
date2|bar2|number2
date1|foo1|number1
date2|foo2|number2
小さな更新:
LC_ALL = Cをjoinの前で使用することにより、- HåkonHægland のベンチマークでわかるように、物事は本当にスピードアップします
PS1:結合がgrep -fよりも高速であるかどうか、私には疑問があります...
このスレッドは終了しましたが、2つのファイル間のすべてのgrepに似たメソッドがこの投稿に収集されています。このawkの代替を追加して、賞金を獲得したInianのawkソリューションと同様(またはさらに改善)にしてください。
awk 'NR==FNR{a[$0]=1;next}a[$2]' patterns.txt FS="|" datafile.txt >matches.txt # For matches restricted on Field2 of datafile
これはInian awk $2 in hash
ソリューションと同等ですが、ハッシュ配列全体に$ 2のfile2が含まれているかどうかをawkに要求しないため、さらに高速になる可能性があります。a[$ 2]が値かどうか。
ハッシュ配列の作成から最初のパターンファイルappartを読み取るときに、値も割り当てます。
データファイルの$2
が以前にパターンファイルで見つかった場合、a[$2]
には値があり、nullではないため出力されます。
データファイルのa[$2]
が値を返さない場合(null)、これはfalseに変換されます=>印刷されません。
データファイルの3つのフィールドのいずれかに一致する拡張子:
awk 'NR==FNR{a[$0]=1;next}(a[$1] || a[$2] || a[$3])' patterns.txt FS="|" datafile.txt >matches.txt. #Printed if any of the three fields of datafile match pattern.
どちらの場合も、awkの前にLC_ALL = Cを適用すると、処理が速くなるようです。
PS1:このソリューションにはもちろん、すべてのawkソリューションの落とし穴があります。パターンマッチングではありません。ここにあるほとんどのソリューションのように、2つのファイル間の直接/固定マッチングです。
PS2: HåkonHægland の小さなベンチマークファイルを使用した貧弱なマシンベンチマークでは、awk 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt
に比べてパフォーマンスが約20%向上しています。
私はSQLite3を使用します:)おそらくメモリ内データベースか何か。ファイルをインポートし、SQLクエリを使用します。
flexの使用:
$ awk 'NR==1{ printf "%%%%\n\n.*\\|(%s",$0 }
{ printf "|%s",$0 }
END { print ")\\|.*\\n ECHO;\n.*\\n ;\n%%\n" }' file1.txt > a.fl
$ flex -Ca -F a.fl ; cc -O Lex.yy.c -lfl
$ a.out < file2.txt > out
コンパイル(cc ...)は遅いプロセスです。このアプローチは、安定したfile1.txtの場合にのみ支払います
(私のマシンでは)このアプローチで "100 in 10_000_000"テストを実行するのにかかる時間はLC_ALL=C fgrep...
より3倍高速です。
これにはPerlを使用することもできます。
これによりメモリが占有され、マシン/サーバーにある程度の空き容量があることに注意してください。
サンプルデータ:
%_STATION@gaurav * /root/ga/pl> head file1.txt file2.txt
==> file1.txt <==
foo1
foo2
...
bar1
bar2
...
==> file2.txt <==
date1|foo1|number1
date2|foo2|number2
date3|foo3|number3
...
date1|bar1|number1
date2|bar2|number2
date3|bar3|number3
%_STATION@gaurav * /root/ga/study/pl>
スクリプト出力:スクリプトは、output_comp
という名前のファイルに最終出力を生成します。
%_STATION@gaurav * /root/ga/pl> ./comp.pl file1.txt file2.txt ; cat output_comp
date1|bar1|number1
date2|bar2|number2
date2|foo2|number2
date1|foo1|number1
%_STATION@gaurav * /root/ga/pl>
スクリプト:
%_STATION@gaurav * /root/ga/pl> cat comp.pl
#!/usr/bin/Perl
use strict ;
use warnings ;
use Data::Dumper ;
my ($file1,$file2) = @ARGV ;
my $output = "output_comp" ;
my %hash ; # This will store main comparison data.
my %tmp ; # This will store already selected results, to be skipped.
(scalar @ARGV != 2 ? (print "Need 2 files!\n") : ()) ? exit 1 : () ;
# Read all files at once and use their name as the key.
for (@ARGV) {
open FH, "<$_" or die "Cannot open $_\n" ;
while (my $line = <FH>) {chomp $line ;$hash{$_}{$line} = "$line"}
close FH ;
}
# Now we churn through the data and compare to generate
# the sorted output in the output file.
open FH, ">>$output" or die "Cannot open outfile!\n" ;
foreach my $k1 (keys %{$hash{$file1}}){
foreach my $k2 (keys %{$hash{$file2}}){
if ($k1 =~ m/^.+?$k2.+?$/) {
if (!defined $tmp{"$hash{$file2}{$k2}"}) {
print FH "$hash{$file2}{$k2}\n" ;
$tmp{"$hash{$file2}{$k2}"} = 1 ;
}
}
}
}
close FH ;
%_STATION@gaurav * /root/ga/pl>
ありがとう。
私見、grepは巨大なfile2.txt向けに高度に最適化された優れたツールですが、それほど多くのパターンを検索することはできません。 file1.txtのすべての文字列を\ | bar1 | bar2 | foo1 | foo2\|のような単一の巨大な正規表現に結合することをお勧めします
echo '\|'$(paste -s -d '|' file1.txt)'\|' > regexp1.txt
grep -E -f regexp1.txt file2.txt > file.matched
そしてもちろんLANG = Cが役立つかもしれません。自分でテストできるように、フィードバックを送信するか、ファイルを送信してください。
可能な方法はpython
を使用することです:
$ cat test.py
import sys,re
with open(sys.argv[1], "r") as f1:
patterns = f1.read().splitlines() # read pattern from file1 without the trailing newline
m = re.compile("|".join(patterns)) # create the regex
with open(sys.argv[2], "r") as f2:
for line in f2:
if m.search(line) :
print line, # print line from file2 if this one matches the regex
次のように使用します。
python test.py file1.txt file2.txt
言語などを設定することは、おそらく少し役に立ちます。
そうでなければ、私はあなたの基本的な問題から逃れるための魔法の解決策を考えることができません:データは構造化されていないので、file1の行数にfile2の行数を掛けた結果になる検索があります。
数十億行をデータベースに入れ、それをスマートな方法でインデックス付けすることは、私が考えることができる唯一のスピードアップです。ただし、そのインデックスは非常にスマートでなければなりません。
簡単な解決策は、すべてを収めるのに十分なメモリがあることです。そうでなければ、これについてあなたができることはこれ以上ありません...