我想创建一个字典,第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]
答案 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_vals
和not_found_vals
中的列表中进行搜索。实际上,内存使用情况,AFAIK将仅取决于您要搜索的val数量,而不取决于文件的大小。
我认为并行化此任务的最简单方法就是将文件拆分并在每个部分中使用不同的进程单独搜索。这样你就不需要处理线程之间的通信(我认为更容易,更快)。
这是一个很好的方法,因为我推断你使用BASH(你使用grep:P)是this answer中提到的:
split -l 1000000 filename
将生成每个1000000行的文件。
您可以轻松修改脚本,将匹配项保存到每个进程的新文件中,然后合并不同的输出文件。