使用大过滤器过滤大文件

使用大过滤器过滤大文件

我想提取$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线程持续占用 12.3 GB 内存。我假设这个需求来自 grep 构建的字典$file2

如何更有效地实现这样的过滤器?

答案1

它被覆盖在man parallel https://www.gnu.org/software/parallel/man.html#示例:-Grepping-n-lines-for-m-regular-expressions

示例:在 n 行中查找 m 正则表达式。

grep 大量正则表达式的大文件的最简单解决方案是:

grep -f regexps.txt bigfile

或者如果正则表达式是固定字符串:

grep -F -f regexps.txt bigfile

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

RAM 很容易测量:如果 grep 进程占用了大部分可用内存(例如运行 top 时),那么 RAM 就是一个限制因素。

CPU 也很容易测量:如果 grep 在 top 中占用 >90% 的 CPU,那么 CPU 是一个限制因素,并行化将加快这一速度。

很难看出磁盘 I/O 是否是限制因素,并且根据磁盘系统,并行化可能会更快或更慢。唯一确定的方法是测试和测量。

限制因素:内存

无论大文件的大小如何,正常的 grep -f regexs.txt 大文件都可以工作,但是如果 regexps.txt 太大而无法放入内存,那么您需要将其拆分。

grep -F 大约需要 100 字节的 RAM,而 grep 每 1 字节的正则表达式大约需要 500 字节的 RAM。因此,如果 regexps.txt 占 RAM 的 1%,那么它可能太大了。

如果您可以将正则表达式转换为固定字符串,请执行此操作。例如,如果您在大文件中查找的行全部如下所示:

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 线程数,对于 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 启动一个 grep 并为每个 CPU 读取一次大文件,但由于这是并行完成的,除了第一个读取之外的所有读取都将缓存在 RAM 中。根据 regexp.txt 的大小,使用 --block 10m 而不是 -L1000 可能会更快。

一些存储系统在并行读取多个块时性能更好。对于某些 RAID 系统和某些网络文件系统来说确实如此。并行读取大文件:

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

这会将 bigfile 分割成 100MB 的块,并对每个块运行 grep。要并行读取 bigfile 和 regexp.txt,请使用 --fifo 将两者结合起来:

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

如果一行与多个正则表达式匹配,则该行可能会重复。

更大的问题

如果问题太大而无法解决,那么您可能已经准备好使用 Lucene了。

相关内容