我有一个大文件,我需要阅读并从中制作字典。我希望这个尽可能快。但是我在python中的代码太慢了。这是一个显示问题的最小例子。
首先制作一些假数据
paste <(seq 20000000) <(seq 2 20000001) > largefile.txt
现在这里是一段最小的python代码,可以读取它并制作字典。
import sys
from collections import defaultdict
fin = open(sys.argv[1])
dict = defaultdict(list)
for line in fin:
parts = line.split()
dict[parts[0]].append(parts[1])
时序:
time ./read.py largefile.txt
real 0m55.746s
然而,可以更快地读取整个文件:
time cut -f1 largefile.txt > /dev/null
real 0m1.702s
我的CPU有8个内核,是否可以并行化该程序 python加快了吗?
一种可能性是读入大块输入,然后在不同的非重叠子块上并行运行8个进程,使字典与内存中的数据并行,然后在另一个大块中读取。这在python中是否有可能以某种方式使用多处理?
更新即可。假数据不是很好,因为每个键只有一个值。更好的是
perl -E 'say int rand 1e7, $", int rand 1e4 for 1 .. 1e7' > largefile.txt
答案 0 :(得分:5)
有可能将其并行化以加快速度,但并行执行多次读取不太可能有所帮助。
你的操作系统不太可能并行地进行多次读取(例外情况是条纹raid数组,在这种情况下你仍然需要知道最佳使用它的步幅。)
您可以做的是,与读取并行运行相对昂贵的字符串/字典/列表操作。
因此,一个线程读取并将(大)块推送到同步队列,一个或多个消费者线程从队列中提取块,将它们拆分成行,然后填充字典。
(如果您选择多个消费者线程,正如Pappnese所说,每个线程构建一个字典然后加入它们。)
提示:
重新。奖金:
显然没有GIL可以应对,因此多个消费者可能会更好地扩展。但是,读取行为不会改变。缺点是C缺乏对哈希映射的内置支持(假设您仍然需要Python样式字典)和同步队列,因此您必须找到合适的组件或编写自己的组件。 多个消费者各自建立自己的字典然后在最后合并它们的基本策略仍然是最好的。使用strtok_r
代替str.split
可能会更快,但请记住,您还需要手动管理所有字符串的内存。哦,你需要逻辑来管理线段。老实说C给了你很多选择,我认为你只需要对它进行分析和看。
答案 1 :(得分:3)
似乎很有可能认为使用处理池会解决这样的问题,但最终会比这更复杂,至少在纯Python中是这样。
因为OP提到每个输入行上的列表在实践中比两个元素更长,所以我使用以下命令创建了一个更加逼真的输入文件:
paste <(seq 20000000) <(seq 2 20000001) <(seq 3 20000002) |
head -1000000 > largefile.txt
在分析原始代码之后,我发现该过程中最慢的部分是行分割例程。 (.split()
比我机器上的.append()
花了大约2倍。)
1000000 0.333 0.000 0.333 0.000 {method 'split' of 'str' objects}
1000000 0.154 0.000 0.154 0.000 {method 'append' of 'list' objects}
所以我考虑拆分成另一个函数并使用池来分配分割字段的工作:
import sys
import collections
import multiprocessing as mp
d = collections.defaultdict(list)
def split(l):
return l.split()
pool = mp.Pool(processes=4)
for keys in pool.map(split, open(sys.argv[1])):
d[keys[0]].append(keys[1:])
不幸的是,添加池会使速度降低2倍以上。原始版本看起来像这样:
$ time python process.py smallfile.txt
real 0m7.170s
user 0m6.884s
sys 0m0.260s
与并行版本相比:
$ time python process-mp.py smallfile.txt
real 0m16.655s
user 0m24.688s
sys 0m1.380s
因为.map()
调用基本上必须序列化(pickle)每个输入,将它发送到远程进程,然后从远程进程反序列化(unpickle)返回值,这样使用池是很多的慢点。通过向池中添加更多内核可以获得一些改进,但我认为这从根本上说是分配这项工作的错误方法。
为了真正加快核心速度,我的猜测是你需要使用某种固定的块大小读入大块的输入。然后,您可以将整个块发送到工作进程并返回序列化列表(尽管仍然不知道此处的反序列化将花费多少)。读取固定大小的块中的输入听起来似乎对预期的输入很棘手,但是,因为我的猜测是每行不一定是相同的长度。
答案 2 :(得分:3)
几年前,在Tim Bray的网站上有一篇关于这篇博文的系列文章“Wide Finder Project”[1]。你可以找到ElementTree [3]的Fredrik Lundh和PIL [4]成名的解决方案[2]。我知道在这个网站上通常不鼓励发布链接,但我认为这些链接比复制粘贴代码给你更好的答案。
[1] http://www.tbray.org/ongoing/When/200x/2007/10/30/WF-Results
[2] http://effbot.org/zone/wide-finder.htm
[3] http://docs.python.org/3/library/xml.etree.elementtree.html
[4] http://www.pythonware.com/products/pil/
答案 3 :(得分:1)
你可以尝试的一件事是从文件中获取行数,然后生成8个线程,每个线程从文件的1/8开始生成一个字典,然后在所有线程完成后加入字典。如果是需要时间而不是读取线条的附加物,这可能会加快速度。
答案 4 :(得分:1)
慢速字典追加的更多基数解决方案:用字符串对替换字典。填写然后排序。
答案 5 :(得分:-1)
如果您的数据存档不经常更改,您可以选择序列化它。 Python解释器将更快地反序列化它。 您可以使用cPickle模块。
或者创建8个单独的进程是另一种选择。 因为,只有一个词典使它更有可能。 您可以通过“多处理”模块中的管道或“套接字”模块在这些进程之间进行交互。
祝你好运
BarışÇUHADAR。