(这个问题是关于如何让multiprocessing.Pool()运行代码更快的。我最终解决了这个问题,最终解决方案可以在本帖底部找到。)
原问题:
我正在尝试使用Python将一个单词与列表中的许多其他单词进行比较,并检索出最相似的单词列表。为此,我使用difflib.get_close_matches函数。我使用的是相对较新且功能强大的Windows 7笔记本电脑,配备Python 2.6.5。
我想要加速比较过程,因为我的单词比较列表非常长,而我必须多次重复比较过程。当我听说有关多进程模块时,如果可以将比较分解为工作任务并同时运行(从而利用机器功率以换取更快的速度),我的比较任务将更快地完成,这似乎很合理。
但是,即使尝试了许多不同的方式,并使用已经显示在文档和论坛帖子中的方法,Pool方法似乎仍然非常缓慢,比一次在整个列表上运行原始get_close_match函数要慢得多。我想要理解为什么Pool()如此缓慢,以及我是否使用它正确。我仅将这个字符串比较方案用作示例,因为那是我最近无法理解或使多处理工作的示例,而不是反对我。下面只是difflib场景中的示例代码,显示了普通方法和Pooled方法之间的时间差异:
from multiprocessing import Pool
import random, time, difflib
# constants
wordlist = ["".join([random.choice([letter for letter in "abcdefghijklmnopqersty"]) for lengthofword in xrange(5)]) for nrofwords in xrange(1000000)]
mainword = "hello"
# comparison function
def findclosematch(subwordlist):
matches = difflib.get_close_matches(mainword,subwordlist,len(subwordlist),0.7)
if matches <> []:
return matches
# pool
print "pool method"
if __name__ == '__main__':
pool = Pool(processes=3)
t=time.time()
result = pool.map_async(findclosematch, wordlist, chunksize=100)
#do something with result
for r in result.get():
pass
print time.time()-t
# normal
print "normal method"
t=time.time()
# run function
result = findclosematch(wordlist)
# do something with results
for r in result:
pass
print time.time()-t
要找的单词是“hello”,要查找的单词列表是一个由5个随机字符组成的100万项长列表(仅用于说明目的)。我使用3个处理器核心和映射函数,块大小为100(我认为是每个工作程序要处理的列表项?)(我还尝试过1000和10000的块大小,但没有真正的区别)。请注意,在两种方法中,我都在调用函数之前启动计时器,并在循环遍历结果后立即结束计时器。如下所示,计时结果明显有利于原始的非池方法:
>>>
pool method
37.1690001488 seconds
normal method
10.5329999924 seconds
>>>
池方法比原始方法慢近4倍。我是否遗漏了什么,或者对池化/多进程工作方式的理解可能存在误解?我怀疑问题的一部分可能是map函数返回None,因此即使我只想返回实际匹配项并已在函数中编写为此类结果,它也会向结果列表添加数千个不必要的项。据我所知,这就是map的工作方式。我听说过一些其他函数(如filter)只收集非False结果,但我认为multiprocessing/Pool不支持filter方法。除了multiprocessing模块中的map/imap之外,还有其他函数可以帮助我仅返回函数返回的内容吗?根据我的理解,apply函数更适用于提供多个参数。
我知道还有imap函数,但没有任何时间改进。原因是我遇到了理解itertools模块有多好的同样问题,它被认为是“闪电般快速”的,我注意到调用函数的确很快,但根据我的经验和我所读的,那是因为调用函数实际上并未进行任何计算,因此当需要迭代结果以收集和分析它们时(如果没有这些,调用该函数就没有意义),它所需的时间与直接使用函数的普通版本相同或甚至更长。但我想这是另一个帖子的问题了。
无论如何,很高兴看到有人能在正确的方向上给我指引,并真的非常感谢任何帮助。我更感兴趣的是理解多进程工作方式,而不是让这个示例正常工作,尽管一些示例解决方案代码建议对我的理解会有所帮助。
答案:
似乎减速与附加进程的缓慢启动时间有关。我无法使.Pool()函数足够快。我最终的解决方案是手动拆分工作负载列表,使用多个.Process()而不是.Pool(),并在队列中返回解决方案。但我想知道最关键的变化可能是将工作负载拆分为要查找的主要单词,而不是要进行比较的单词,可能是因为difflib搜索函数已经非常快了。这是新代码同时运行5个进程,结果比运行简单代码(6秒 vs 55秒)快约x10。在difflib已经非常快的情况下,非常适用于快速模糊查找。
from multiprocessing import Process, Queue
import difflib, random, time
def f2(wordlist, mainwordlist, q):
for mainword in mainwordlist:
matches = difflib.get_close_matches(mainword,wordlist,len(wordlist),0.7)
q.put(matches)
if __name__ == '__main__':
# constants (for 50 input words, find closest match in list of 100 000 comparison words)
q = Queue()
wordlist = ["".join([random.choice([letter for letter in "abcdefghijklmnopqersty"]) for lengthofword in xrange(5)]) for nrofwords in xrange(100000)]
mainword = "hello"
mainwordlist = [mainword for each in xrange(50)]
# normal approach
t = time.time()
for mainword in mainwordlist:
matches = difflib.get_close_matches(mainword,wordlist,len(wordlist),0.7)
q.put(matches)
print time.time()-t
# split work into 5 or 10 processes
processes = 5
def splitlist(inlist, chunksize):
return [inlist[x:x+chunksize] for x in xrange(0, len(inlist), chunksize)]
print len(mainwordlist)/processes
mainwordlistsplitted = splitlist(mainwordlist, len(mainwordlist)/processes)
print "list ready"
t = time.time()
for submainwordlist in mainwordlistsplitted:
print "sub"
p = Process(target=f2, args=(wordlist,submainwordlist,q,))
p.Daemon = True
p.start()
for submainwordlist in mainwordlistsplitted:
p.join()
print time.time()-t
while True:
print q.get()
findclosematch()
做更多的工作。否则,pickling / unpickling参数将占主导地位。 - jfsmatches <> []
,那么这种写法很糟糕,请使用if matches:
代替。 - jfs<>
,它已经被废弃了很长时间,在Python3中会引发SyntaxError
,因此使用它会使代码变得不太向前兼容。请注意,生成进程和进程间通信的成本非常高。如果您想通过多个进程减少时间,必须确保计算时间足够长,以便开销不重要。在您的情况下,我认为这并不是真的。 - Bakuriuif matches:
检查是完全无用的,可能会产生错误。我刚试着运行脚本,修改了一些参数,由于这个虚假检查而得到了一个TypeError: NoneType object is not iterable
错误。99.9% 的情况下,函数应该始终返回相同的结果。不要使用None
特殊处理空结果,因为这只会使代码中其他部分处理函数结果变得更加复杂。 - Bakuriu