在python中从文件中grep多个值的最快方法

时间:2014-03-18 13:14:49

标签: python grep

  • 我有一个300米行(inputFile)的文件,所有行都有2个由制表符分隔的列。
  • 我还有1000个独特项目(vals)的列表。

我想创建一个字典,第1列为键,第2列为 inputFile 中所有行的值,其中第一列出现在 vals 中。 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)]

示例 INPUTFILE:

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

vals:

[1,2,594592888]

想要字典:

{2:9913,594592888:32630}

在notFound中:

[1]

3 个答案:

答案 0 :(得分:9)

您在评论中澄清说,每个密钥在数据中最多出现一次。由此可见,事实上只有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表明正则表达式正在运行时,正则表达式中的每次连续改进都会增加每秒IO请求的数量。并且使用加热的页面缓存重新运行grep并且--mmap选项不会加快速度(因为如果文件IO是瓶颈的话可能会这样)。因此,更高的速度可能需要具有更快的正则表达式引擎的实用程序。

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

答案 1 :(得分:0)

这不是非常有效的内存(对于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()))

答案 2 :(得分:0)

如果我理解正确,您不希望任何与您不匹配的文件行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_valsnot_found_vals中的列表中进行搜索。实际上,内存使用情况,AFAIK将仅取决于您要搜索的val数量,而不取决于文件的大小。

编辑:

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

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

split -l 1000000 filename

将生成每个1000000行的文件。

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