首先让我告诉你我目前的设置:
import multiprocessing.pool
from contextlib import closing
import os
def big_function(param):
process(another_module.global_variable[param])
def dispatcher():
# sharing read-only global variable taking benefit from Unix
# which follows policy copy-on-update
# https://stackoverflow.com/questions/19366259/
another_module.global_variable = huge_list
# send indices
params = range(len(another_module.global_variable))
with closing(multiprocessing.pool.Pool(processes=os.cpu_count())) as p:
multiprocessing_result = list(p.imap_unordered(big_function, params))
return multiprocessing_result
这里我使用共享变量在创建进程池之前更新,其中包含大量数据,这确实让我获得了加速,因此它现在似乎没有被腌制。此变量也属于导入模块的范围(如果重要的话)。
当我尝试创建这样的设置时:
another_module.global_variable = []
p = multiprocessing.pool.Pool(processes=os.cpu_count())
def dispatcher():
# sharing read-only global variable taking benefit from Unix
# which follows policy copy-on-update
# https://stackoverflow.com/questions/19366259/
another_module_global_variable = huge_list
# send indices
params = range(len(another_module.global_variable))
multiprocessing_result = list(p.imap_unordered(big_function, params))
return multiprocessing_result
p
“记得”全局共享列表为空,并且在从调度程序内部调用时拒绝使用新数据。
现在问题是:在上面的第一个设置中处理8个核心上的~600个数据对象,我的并行计算运行8秒,而单线程运行12秒。
这就是我的想法:只要多处理pickle数据,我每次都需要重新创建进程,我需要挑选函数big_function()
,所以我就浪费时间了。使用全局变量部分解决了数据的情况(但我仍然需要在每次更新时重新创建池)。
我可以对big_function()
的实例做什么(这取决于其他模块的许多其他功能,numpy等)?我可以一劳永逸地创建os.cpu_count()
个副本,并以某种方式将新数据提供给它们并接收结果,重复使用工作人员吗?
答案 0 :(得分:1)
回过头来回忆一下'问题:
another_module.global_variable = []
p = multiprocessing.pool.Pool(processes=os.cpu_count())
def dispatcher():
another_module_global_variable = huge_list
params = range(len(another_module.global_variable))
multiprocessing_result = list(p.imap_unordered(big_function, params))
return multiprocessing_result
当您创建Pool
实例时,似乎是什么问题。
为什么?
这是因为当你创建Pool
的实例时,它确实设置了一些工作者(默认情况下等于一些CPU核心),并且它们在那时都被启动(分叉)。这意味着工作人员拥有父母全局状态的副本(以及其他所有内容中的another_module.global_variable
),并且通过写时复制策略,当您更新another_module.global_variable
的值时,您可以在父级中更改它处理。工人可以参考旧的价值。这就是你遇到问题的原因。
这是一个小片段,您可以在其中切换全局变量值更改的位置以及启动进程的位置,并检查子进程中打印的内容。
from __future__ import print_function
import multiprocessing as mp
glob = dict()
glob[0] = [1, 2, 3]
def printer(a):
print(globals())
print(a, glob[0])
if __name__ == '__main__':
p = mp.Process(target=printer, args=(1,))
p.start()
glob[0] = 'test'
p.join()
这是Python2.7代码,但它也适用于Python3.6。
这个问题的解决方案是什么?
好吧,回到第一个解决方案。您更新导入的模块的变量的值,然后创建进程池。
现在是缺乏加速的真正问题。
以下是documentation关于如何腌制函数的有趣部分:
请注意,功能(内置和用户定义)被“完全”腌制 合格的“名称参考,而不是价值。这意味着只有 功能名称被腌制,以及模块的名称 函数定义为。函数的代码,也不是函数的代码 功能属性是pickle。因此,定义模块必须是 可以在unpickling环境中导入,并且模块必须包含 命名对象,否则将引发异常。
这意味着你的功能酸洗不应该是浪费时间的过程,或者至少不是它本身。导致加速不足的原因是,对于传递给imap_unordered
调用的列表中的~600个数据对象,您将其中的每一个传递给工作进程。同样,multiprocessing.Pool
的底层实现可能是导致此问题的原因。
如果您深入了解multiprocessing.Pool
实现,您将看到使用Threads
的两个Queue
正在处理父级和所有子级(工作者)进程之间的通信。因此,所有进程都不断需要函数参数并不断返回响应,最终会导致父进程非常繁忙。这就是为什么很多'时间用于调度'将数据传入工作进程和从工作进程传递数据。
该怎么办?
尝试随时增加工作进程中进程的数据对象数。在您的示例中,您将传递一个数据对象,并且可以确保每个工作进程在任何时候都只处理一个数据对象。为什么不增加传递给工作进程的数据对象的数量?这样,您可以通过处理10个,20个甚至更多数据对象来使每个过程更加繁忙。从我所看到的情况来看,imap_unordered
有chunksize
个参数。默认设置为1
。尝试增加它。像这样:
import multiprocessing.pool
from contextlib import closing
import os
def big_function(params):
results = []
for p in params:
results.append(process(another_module.global_variable[p]))
return results
def dispatcher():
# sharing read-only global variable taking benefit from Unix
# which follows policy copy-on-update
# https://stackoverflow.com/questions/19366259/
another_module.global_variable = huge_list
# send indices
params = range(len(another_module.global_variable))
with closing(multiprocessing.pool.Pool(processes=os.cpu_count())) as p:
multiprocessing_result = list(p.imap_unordered(big_function, params, chunksize=10))
return multiprocessing_result
一对建议:
params
作为索引列表,用于在big_function
中选择特定数据对象。您可以创建表示第一个和最后一个索引的元组,并将它们传递给big_function
。这可以成为增加工作量的一种方式。这是我上面提出的方法的替代方法。Pool(processes=os.cpu_count())
,否则您可以省略它。它默认采用CPU核心数。很抱歉答案的长度或可能潜入的任何拼写错误。