我想提取$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了。