web-dev-qa-db-ja.com

大きなフィルターで大きなファイルをフィルタリングする

$file1に格納されている文字列で始まる$file2のすべての行を抽出したいと思います。

$file1は4GBの大きさで約2,000万行、$file2は200万行、約140 MBの大きさで、,で区切られた2つの列が含まれています。両方のファイルの最大行長は1000をはるかに下回り、LC_ALL=Cでソートされ、$file1には\0以外の追加の文字を含めることができます。

意外とこのコマンド

parallel --pipepart -a $file1 grep -Ff $file2

極端な量のメモリを消費し、OSによって強制終了されます。

スレッドの数を制限すると、コマンドは機能します。

parallel --pipepart -j 8 -a $file1 grep -Ff $file2

最後のコマンドで、htopは、各grep -Ff $file2-スレッドが常に12.3GBのメモリを占有していることを示しています。この要求は、grepが$file2からビルドする辞書から来ていると思います。

どうすればそのようなフィルターをより効率的に実現できますか?

2
katosh

man parallelhttps://www.gnu.org/software/parallel/man.html#EXAMPLE:-Grepping-n-lines-for-m-regular-expressions で説明されています。

例:m個の正規表現に対してn行をgrepします。

多くの正規表現の大きなファイルをgrepする最も簡単な解決策は次のとおりです。

grep -f regexps.txt bigfile

または、正規表現が固定文字列の場合:

grep -F -f regexps.txt bigfile

CPU、RAM、ディスクI/Oの3つの制限要因があります。

RAMの測定は簡単です:grepプロセスが空きメモリの大部分を占める場合(たとえば、topを実行している場合)、RAMが制限要因です。

CPUの測定も簡単です。grepが90%を超えるCPUを上に使用している場合、CPUが制限要因であり、並列化によってこれが高速化されます。

ディスクI/Oが制限要因であるかどうかを確認するのは難しく、ディスクシステムによっては、並列化の速度が速い場合と遅い場合があります。確実に知る唯一の方法は、テストして測定することです。

制限要因:RAM

通常のgrep-f regexs.txt bigfileは、bigfileのサイズに関係なく機能しますが、regexps.txtが大きすぎてメモリに収まらない場合は、これを分割する必要があります。

grep-Fは約100バイトのRAMを取り、grepは約500バイトのRAM regexpの1バイトあたりを取ります。したがって、regexps.txtが1%の場合RAMの場合、大きすぎる可能性があります。

正規表現を固定文字列に変換できる場合は、それを実行してください。例えば。 bigfileで探している行がすべて次のようになっている場合:

ID1 foo bar baz Identifier1 quux
fubar ID2 foo bar baz Identifier2

次に、regexps.txtを次の場所から変換できます。

ID1.*Identifier1   
ID2.*Identifier2

に:

ID1 foo bar baz Identifier1
ID2 foo bar baz Identifier2

このようにして、メモリの使用量が約80%少なく、はるかに高速なgrep-Fを使用できます。

それでもメモリに収まらない場合は、次のようにすることができます。

parallel --pipepart -a regexps.txt --block 1M grep -Ff - -n bigfile |
  sort -un | Perl -pe 's/^\d+://'

1Mは、空きメモリをCPUスレッドの数で割り、grep -Fの場合は200で割り、通常のgrepの場合は1000で割る必要があります。 GNU/Linuxでは、次のことができます。

free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 }
          END { print sum }' /proc/meminfo)
percpu=$((free / 200 / $(parallel --number-of-threads)))k

parallel --pipepart -a regexps.txt --block $percpu --compress \
  grep -F -f - -n bigfile |
  sort -un | Perl -pe 's/^\d+://'

重複した行と間違った順序で生活できる場合は、次のことを行う方が高速です。

parallel --pipepart -a regexps.txt --block $percpu --compress \
  grep -F -f - bigfile

制限要因:CPU

CPUが制限要因である場合、並列化は正規表現で実行する必要があります。

cat regexp.txt | parallel --pipe -L1000 --round-robin --compress \
  grep -f - -n bigfile |
  sort -un | Perl -pe 's/^\d+://'

このコマンドは、CPUごとに1つのgrepを開始し、CPUごとに1回bigfileを読み取りますが、並行して実行されるため、最初の読み取りを除くすべての読み取りがRAMにキャッシュされます。 regexp.txtのサイズによっては、-L1000の代わりに--block10mを使用する方が速い場合があります。

一部のストレージシステムは、複数のチャンクを並行して読み取るとパフォーマンスが向上します。これは、一部のRAIDシステムおよび一部のネットワークファイルシステムに当てはまります。 bigfileの読み取りを並列化するには:

parallel --pipepart --block 100M -a bigfile -k --compress \
  grep -f regexp.txt

これにより、bigfileが100MBのチャンクに分割され、これらの各チャンクでgrepが実行されます。 bigfileとregexp.txtの両方の読み取りを並列化するには、-fifoを使用して2つを組み合わせます。

parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \
  \| parallel --pipe -L1000 --round-robin grep -f - {}

行が複数の正規表現に一致する場合、その行は重複している可能性があります。

より大きな問題

問題が大きすぎてこれで解決できない場合は、おそらくLuceneの準備ができています。

1
Ole Tange