Shell-Script

使用大過濾器過濾大文件

  • December 4, 2020

我想提取所有$file1以儲存在$file2.

$file1大小為 4 GB,約有 2000 萬行,$file2有 200 萬行,大小約為 140 MB,包含兩列,用,. 這兩個文件的最大行長度遠低於 1000,它們按順序排序,LC_ALL=C並且$file1可以包含除\0.

沒想到這個命令

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

消耗大量記憶體並被作業系統殺死。

如果我限制執行緒數,該命令有效:

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

對於最後一個命令,htop 顯示每個grep -Ff $file2-thread 始終佔用 12.3 GB 的記憶體。我假設這個需求來自字典 grep 建構於$file2.

我怎樣才能更有效地實現這樣的過濾器?

它包含在man parallel https://www.gnu.org/software/parallel/man.html#EXAMPLE:-Grepping-n-lines-for-m-regular-expressions

範例:為 m 個正則表達式提取 n 行。

為大量正則表達式 grep 大文件的最簡單解決方案是:

grep -f regexps.txt bigfile

或者,如果正則表達式是固定字元串:

grep -F -f regexps.txt bigfile

有 3 個限制因素:CPU、RAM 和磁碟 I/O。

RAM 易於測量:如果 grep 程序佔用了您的大部分空閒記憶體(例如,在執行 top 時),那麼 RAM 是一個限制因素。

CPU 也很容易測量:如果 grep 佔用 >90% 的 CPU,那麼 CPU 是一個限制因素,並行化會加快速度。

很難看出磁碟 I/O 是否是限制因素,並且取決於磁碟系統,並行化可能更快或更慢。唯一確定的方法是測試和測量。

限制因素:RAM

普通的 grep -f regexs.txt 大文件不管大文件的大小都可以工作,但是如果 regexps.txt 太大而無法放入記憶體,那麼您需要將其拆分。

grep -F 佔用大約 100 字節的 RAM,而 grep 每 1 個字節的正則表達式佔用大約 500 字節的 RAM。所以如果 regexps.txt 是你 RAM 的 1%,那麼它可能太大了。

如果您可以將您的正則表達式轉換為固定字元串,請執行此操作。例如,如果您在 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

通過這種方式,您可以使用 grep -F 減少大約 80% 的記憶體並且速度更快。

如果它仍然不適合記憶體,你可以這樣做:

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

1M 應該是您的可用記憶體除以 CPU 執行緒數,然後除以 200(grep -F)和 1000(普通 grep)。在 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 啟動一個 grep 並為每個 CPU 讀取一次大文件,但由於這是並行完成的,除第一個之外的所有讀取都將記憶體在 RAM 中。根據 regexp.txt 的大小,使用 –block 10m 而不是 -L1000 可能更快。

一些儲存系統在並行讀取多個塊時性能更好。這適用於某些 RAID 系統和某些網路文件系統。並行讀取大文件:

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

這會將大文件拆分為 100MB 的塊並在每個塊上執行 grep。要並行讀取 bigfile 和 regexp.txt,請使用 –fifo 將兩者結合起來:

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

如果一行匹配多個正則表達式,則該行可能重複。

更大的問題

如果問題太大而無法通過此方法解決,那麼您可能已經為 Lucene 做好了準備。

引用自:https://unix.stackexchange.com/questions/500676