【问题标题】:Fastest way to grep multiple values from file in python在python中从文件中grep多个值的最快方法
【发布时间】:2014-03-18 13:14:48
【问题描述】:
  • 我有一个 300m 行的文件 (inputFile),所有文件都有 2 列,由制表符分隔。
  • 我还有一个包含 1000 个唯一项目 (vals) 的列表。

我想为 inputFile 中第一列出现在 vals 中的所有行创建一个字典,其中第 1 列作为键,第 2 列作为值。 vals 中的一些项目不会出现在文件中,这些值必须保存在新列表中。我最多可以使用 20 个线程来加快这个过程。

实现这一目标的最快方法是什么?

到目前为止我最好的尝试:

newDict = {}
foundVals = []
cmd = "grep \"" + vals[0]
for val in vals:
     cmd = cmd + "\|^"+val+"[[:space:]]"
cmd = cmd + "\" " + self.inputFile
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in iter(p.stdout.readline, ''):
    info = line.split()
    foundVals.append(info[0])
    newDict.update({info[0]:info[1]})
p.wait()
notFound = [x for x in vals if x not in set(foundVals)]

示例 输入文件:

2       9913
3       9913
4       9646
...
594592886       32630
594592888       32630
594592890       32630

值:

[1,2,594592888]

想要的字典:

{2:9913,594592888:32630}

并且在未找到:

[1]

【问题讨论】:

  • 您能否提供输入文件的样本和所需的输出?只是为了说清楚。
  • 也许可以尝试使用 python 的 open(file, r) 命令,看看是否能提高性能。
  • @sshashank124 这是我的第一次尝试,但这种方法要快得多......
  • @jeanrjc 添加了一个示例
  • @MarkRansom 正在写这个!谢谢@Jetse:关键点是,如果你包含val[0] 而没有锚和空格字符类,你可能会得到一个错误的命中。

标签: python grep


【解决方案1】:

您在评论中澄清说,每个键在数据中最多出现一次。从这一点以及只有 1000 个键的事实可以看出,在 Python 中完成的工作量是微不足道的;几乎所有的时间都花在等待grep 的输出上。这很好;您将线路提取委托给专门的实用程序的策略仍然是合理的。但这意味着必须在行提取方面找到性能提升。

您可以通过优化正则表达式来加快速度。例如,而不是

^266[[:space:]]\|^801[[:space:]]\|^810[[:space:]]

你可以使用:

^\(266\|801\|810\)[[:space:]]

这样就不必为每个备选方案单独匹配锚点。我发现测试数据(1000 万行,25 个键)随着这种变化而提高了大约 15%。

进一步的优化是统一替换中的常用前缀:266\|801\|810 可以替换为等效的266\|8\(01\|10\)。以这种方式重写 25 键正则表达式可以使测试数据的速度提高近 50%。

此时grep 开始显示其极限。似乎它受 CPU 限制:iostat 表明正则表达式的每一次连续改进都会增加 grep 运行时每秒的 IO 请求数。并且使用预热的页面缓存和 --mmap 选项重新运行 grep 并不会加快速度(如果文件 IO 是瓶颈,它可能会这样)。因此,更快的速度可能需要具有更快正则表达式引擎的实用程序。

其中一个是ag(source here),它的正则表达式实现也执行自动优化,所以你不需要做太多的手动调整。虽然我无法在我的机器上让grep 在不到 12 秒的时间内处理测试数据,但对于上述所有正则表达式变体,ag 在 ~0.5 秒内完成。

【讨论】:

  • 您的 ag 命令看起来如何?我安装了 ag,当使用与 grep 中完全相同的正则表达式时,ag 在 grep 执行时一无所获...我使用了 grep "^(266\|801\|810)[[:space:]]" file.txt 和: ag "^(266\|801\|810)[[:space:]]" file.txt
  • ag 对正则表达式使用 PCRE 语法,因此您不应转义交替运算符(|)。另外,我不确定它是否支持命名字符类;您可能需要将[[:space]] 替换为\s。顺便说一句,对于grep,您还需要转义括号。
【解决方案2】:

如果我理解正确,您不想要任何与您不匹配的文件行vals 由于您谈论的是巨大的文件和数量相当少的想要值,我会选择类似的东西:

vals_set = set(vals)
found_vals = {}

with open(inputfile,"r") as in_file:
    for line in in_file:
        line = line.split() # Assuming tabs or whitespaces
        if line[0] in vals_set:
            found_vals[line[0]] = line[1]


not_found_vals = vals_set.difference(found_vals)

这将是记忆保守的,你会在found_vals 中输入你的字典,在not_found_vals 中输入你的列表。事实上,内存使用情况,AFAIK 将仅取决于您要搜索的 val 数量,而不是文件的大小。

编辑:

我认为并行化此任务的最简单方法是拆分文件并使用不同的进程在每个部分中单独搜索。这样你就不需要处理线程之间的通信(我认为更容易和更快)。

一个很好的方法,因为我推断您正在使用 BASH(您使用 grep :P )是this answer 中提到的:

split -l 1000000 filename

将生成每个文件有 1000000 行。

您可以轻松修改脚本,将匹配项保存到每个进程的新文件中,然后合并不同的输出文件。

【讨论】:

  • 我明白了......嗯......我会调查它并编辑我的答案。但是,我已经对其进行了编辑,提出了一种使用多 CPU 的简单方法
【解决方案3】:

这不是非常节省内存(对于 3 亿行的文件,这可能会导致问题)。除了保存所有值(或读取文件两次)之外,我想不出一种方法来在理解中保存未找到的值。我认为线程不会有太大帮助,因为文件 I/O 可能会成为性能瓶颈。 我假设制表符是文件中的分隔符。 (你没有说,但示例数据看起来有一个选项卡。)

vals = [1,2,594592888]

with open(self.inputfile,'r') as i_file:
    all_vals = {
        int(t[0]):int(t[1])
        for t in (
                line.strip().split('\t')
                for line in i_file
        )
    }

newDict = {
    t[0]:t[1] for t in filter(lambda t: t[0] in vals, all_vals.items())
}

notFound = list(set(all_vals.keys()).difference(newDict.keys()))

【讨论】:

  • IO 瓶颈正是在 Python 中线程确实有帮助的那种情况。
  • 这种方法确实内存效率低(超过30G),没有问题。仍然 grep 快得多...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-08
  • 2013-09-04
  • 1970-01-01
  • 2011-01-20
  • 1970-01-01
  • 2017-02-12
  • 1970-01-01
相关资源
最近更新 更多