zgrep 在多个文件中性能缓慢

zgrep 在多个文件中性能缓慢

我有一个9.8GBgzip 文件 A.gz 和我拥有的其他文件是79MBB.txt 每行都有一些文本。我想在 A.gz 中 grep B 的文本并写入一个新文件。

最初,我使用这个命令

zgrep -f B.txt A.gz > C.xml

但这个命令被挂起,并在很长一段时间内创建了一个空的 C.xml。

然后在谷歌搜索后我了解到,因为 B.txt 很大,所以当它将文本保留在缓冲区中时,它会挂起。

所以我将文本文件分成每个 20000 个文本

split -l 20000 -a 4 B.txt B

我创建了像 Baaaa、Baaab 这样的文件......

然后迭代每个文件

cd B
for f in B*; do
  zgrep -f "$f" ../A.gz >> C.xml
done

它非常慢并且仍在运行。

对此有更好的方法吗?

压缩 gz 文件会提高性能吗?

更新

我尝试使用-F

zgrep -F -f "$f" ../A.gz >> C.xml

这有点快,但仍然想要其他选择

我有像这样的xml

<root>
   <source>source1</source>
   <Id>123</Id>
   <category>ABC</category>
</root>
<root>
    <source>source2</source>
    <Id>123</Id>
    <category>XYZ</category>
</root>

这里的 id 是相同的 123 但类别不同 ABC 和 XYZ

(输入是有限的类别集,例如 ABC、DEF、GHI、JKLM、NOP)最初我的类别为 ABC,因此根据类别 ABC,我找到它的 id,即 123,这样我继续写属于这些的所有 id将类别输入到新文件,即 B.txt(id 列表),如下所示

zgrep -E 'ABC|DEF|GHI|JKLM|NOP' A.gz | sed -n 's:.*<Id>\(.*\)</Id>.*:\1:p' | uniq > B.txt

稍后我迭代这个 id 并获取所有 xml,这样我就获得了属于 id 123 的类别 ABC 和 XYZ 的 xml 标签

答案1

79MB 的 grep“字符串”使用起来会很痛苦。这些行是B.txt真正的正则表达式还是固定的相同字符串?如果它们是固定字符串,它们在A.gz整行中是否显示相同?未压缩中的多少行A.gz预计与 中的行匹配B.txt

模式匹配建议

如果中的行B.txt确实是正则表达式或行的子字符串,A.gz您可能会被迫使用类似的东西超扫描它旨在处理巨大的正则表达式。如果您有足够的磁盘空间,您可以解压缩A.gz,然后让 HyperScan 开始工作(您甚至可以让 shell 在 HyperScan 搜索时即时解压缩)。另一种尝试的替代方法是ripgrep

全线搭配建议

如果您正在处理固定的全行字符串,B.txt并且未压缩的字符串A.gz包含相对较小(假设为 100MB 左右)的匹配行,那么编写一个程序进行预处理可能会更好A.gz

  • 您可以对每一行进行散列B.txt并记住散列
  • 然后,您检查未压缩哈希值中的任何行是否A.gz与之前的任何哈希值相同。如果是这样,您打印出该行(例如进入C.txt)以准备进一步处理
  • 现在,您将进行最后一次检查,更严格地检查每一行是否在B.txt其中C.txt(反之亦然 - 取决于哪个文件较小)

进行初始近似过滤的一些代码可能是这样的:

# Do a quick APPROXIMATE filter of lines in FILENEEDLES that are also in
# FILEHAYSTACK
import sys

def main():
    if len(sys.argv) < 2:
        print("usage: %s FILENEEDLES FILEHAYSTACK" % sys.argv[0])
        exit(1)

    first_filename = sys.argv[1]
    second_filename = sys.argv[2]

    line_hashes = set()

    with open(first_filename, "r") as f:
        for line in f:
            line_hashes.add(hash(line))

    with open(second_filename, "r") as f:
        for line in f:
            if hash(line) in line_hashes:
                sys.stdout.write(line)

if __name__ == "__main__":
    main()

例如:

$ echo -e '1\n2\n3' > B.txt
$ echo -e '2\n3\n4\5' | gzip > A.gz
$ ./approxfilter.py B.txt <(gzip -dc A.gz) > candidates.txt
$ cat candidates.txt
2
3

您现在需要检查candidates.txt以查看行输出是否完全匹配B.txt(但这希望是一个更小且更容易的问题,如果候选行的数量“小”,您甚至可以修改上面的程序来完成这一切“并且完全在内存中可以保存的范围内)。 (提问者后来在评论中澄清,他们不使用全行长度字符串,因此这种方法不起作用)

答案2

您的第二次尝试很可能通过解压缩得到改进,否则循环的每次迭代都将具有完整的解压缩开销 - 提前解压缩将意味着您只有一次该开销。

如果这仍然不够快,您也可以尝试多线程(假设解压 A)。

find B -type f -name 'B*' -print0 \
  | xargs -0 -t -n1 -P8 \
  grep -f {} A >> C.xml

此示例应同时运行 8 个进程,您可能需要根据您拥有的处理器/核心数量调整此值。

我不确定您期望的速度结果是什么样的;坦率地说,听起来你给了它很多工作要做,而很多工作都需要时间。

答案3

zgrep只是 的 shell 脚本包装器grep。它只是运行grep您系统上安装的任何内容并用于gzip解压缩输入文件。

如果您使用的是 GNU grep 版本 3.5 或 3.6,最近发现了一个错误,该错误似乎会减慢模式文件的处理速度,正如您所描述的,性能极度下降。

grep 3.7 发行说明中的​​错误示例模式文件大约有 48 Mb 的模式,因此据我所知,大小应该不是问题。

https://www.theregister.com/2021/08/16/gnu_grep_37/

相关内容