$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
からビルドする辞書から来ていると思います。
どうすればそのようなフィルターをより効率的に実現できますか?
man parallel
https://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の準備ができています。