我正在尝试使用Python的多处理程序包测量我已经“并行化”的代码段,特别是使用Process函数。
我想要并行运行两个函数:function1
和function2
。 function1
不会返回值,而function2
会返回值。 function2的返回值是一个相当大的类实例。
这是我现有的使用队列并行化和获取返回值的代码:
import multiprocessing as mpc
...
def Wrapper(self,...):
jobs = []
q = mpc.Queue()
p1 = mpc.Process(target=self.function1,args=(timestep,))
jobs.append(p1)
p2 = mpc.Process(target=self.function2,args=(timestep,arg1,arg2,arg3,...,q))
jobs.append(p2)
for j in jobs:
j.start()
result = q.get()
for j in jobs:
j.join()
所以,这是我看到的问题。如果我删除了对result = q.get()
的调用,那么执行Wrapper函数所花费的时间会显着减少,因为它不会从function2
返回类,但是我显然无法得到我需要的数据功能。如果我把它重新放入,运行时间会显着增加,从而表明并行化实际上需要比顺序执行这两个函数更长的时间。
以下是Wrapper的一些平均执行时间,供参考:
顺序代码(即function1(timestep)
,res = function2(timestep,a1,a2,a3,...,None)
):10秒
不使用队列的并行化代码:8秒
使用队列的并行化代码:60秒
我对此代码的目标是展示如何并行化一段代码可以改善在不必要的并行函数中执行所需的时间。作为参考,我正在使用cProfile包,生成我的代码的配置文件,并查看Wrapper运行所需的时间。
我开始对整个过程感到沮丧。它旨在基本上加速我已添加到内部开发的现有自定义框架的程序部分,但是我无法在物理上表明我不会增加太多开销。
如果我查看程序的总体执行时间,并行化代码运行得更快。但是,当我深入挖掘时,我的并行化代码开始显得需要更长时间。
现在,我的想法是Queue正在进行某种深度复制操作,但是我找不到引用来说明这个事实,所以我假设它返回一个浅拷贝,对我来说,不应该不需要这样的开销。
答案 0 :(得分:3)
当您将对象传递到multiprocessing.Queue
时,需要在put
侧进行pickle,然后必须将pickle字节刷新到管道。在get
侧,需要从管道中读取pickle字节,然后需要将它们取消回到Python对象中。所以实际上,multiprocessing.Queue
正在做一些比深拷贝更慢的事情。
您看到的开销几乎肯定是取消大型对象所需开销的结果。这是一个并行编程的领域,Python真正在努力 - 如果你正在进行CPU绑定操作(因此不能使用线程来获得并行性)并且需要共享状态,那么你将会去支付性能损失。如果您正在共享大型对象,那么惩罚也可能很大。 Python中的并行性是通过并行化一些CPU绑定操作所带来的性能提升与必须在进程之间共享状态所获得的性能惩罚之间的权衡。因此,您的目标必须是最小化共享状态的数量,并最大化并行化的工作量。
一旦你完成了这项工作,不幸的是,你进一步降低性能影响的选择有些限制。您可以尝试将类转换为ctypes
对象,这将允许您使用multiprocessing.sharedctypes
在共享内存中创建对象。这应该比通过Queue
返回对象更快,但您必须处理ctypes
的所有限制。
另一个想法是在multiprocessing.Manager
服务器中创建对象。如果这样做,您的实际对象将存在于服务器进程中,您的父进程和子进程都将通过Proxy
访问该对象。但是,这会使对象的每次读/写速度变慢,因此最终它可能不会比现在的Queue
实现更好。
这些替代方案都不是很好,并且它可能都不适用于您的用例,在这种情况下,Python可能不是解决此特定问题的最佳语言。不要误解我的意思;我喜欢Python并尽可能地使用它,但这是一个真正困难的领域。