【发布时间】:2018-01-20 00:18:05
【问题描述】:
我在 Python 中使用 multiprocessing 模块,预计启动进程、创建队列以及将值放入队列或从队列中获取值会产生一些开销。但是,如果子流程有足够的工作要做,我希望开销最终会被淘汰。运行一个简单的示例(如下所述),我生成的进程的运行时间大约是从父进程启动的同一进程的 10 倍,即使对于非常大的作业也是如此。
在下面的代码中,我计算了一系列越来越大的数组的平均值。我比较了从父进程调用numpy.mean 与从单个衍生进程调用相同的平均函数以及在衍生进程中什么都不做(以了解开销成本)。
最初,结果如我所料。当从父进程调用mean 时,总运行时间比从衍生进程调用时快得多。对于小型作业,生成进程的运行时间主要由开销决定。
然而,令人惊讶的是,对于较大的作业,衍生进程的运行时间始终超过从父进程调用的成本大约 10 倍。
谁能解释一下?这是由于子进程中的内存限制吗?我测试的最大阵列是 125MB、500MB 和 2GB。
代码如下:
%matplotlib
import numpy, multiprocessing, pandas
def do_nothing(x,q):
q.put(x[-1])
def my_mean(x,q):
q.put(numpy.mean(x))
def test_mp(f,x):
q = multiprocessing.Queue()
p = multiprocessing.Process(target=f,args=(x,q))
p.start()
p.join()
s = q.get()
return s
ndata = 2**numpy.arange(10,29,2)
tr1,tr2,tr3 = [[],[],[]]
for n in ndata:
x = numpy.random.rand(n)
tresults = %timeit -n 1 -r 5 -o -q test_mp(do_nothing,x)
tr1.append(tresults)
tresults = %timeit -n 1 -r 5 -o -q test_mp(my_mean,x)
tr2.append(tresults)
tresults = %timeit -n 1 -r 5 -o -q numpy.mean(x)
tr3.append(tresults)
print("All done")
t1,t2,t3 = map(lambda tr : pandas.Series([1000*t.best for t in tr]),[tr1,tr2,tr3])
df = pandas.DataFrame({'n' : ndata, 't1 (do nothing)' : t1,
't2 (my_mean)' : t2,
't3 (mean)' : t3})
display(df)
df.plot(x='n',style='.-',markersize=10,logx=True,logy=True)
这是结果。所有计时结果都以毫秒为单位。
【问题讨论】:
-
每个进程都有自己的内存空间。队列通过pickling(在第一个进程的内存空间中)和再次unpickling(在第二个进程的内存空间中)在进程之间传输数据。对于庞大的数据结构,这将产生相应的巨大开销。如果可能,您需要初始化数据并在同一进程中处理它。 subprocess 包中还有一些方法(例如 Array),用于跨进程共享某些类型的数据。
-
令我惊讶的是
do_nothing的时间安排:由于您不是多线程的,因此数据必须从一个进程传递到另一个进程,这与n的开销成正比。如果我重复这个实验,我会得到不同的结果,在第 8 行显示do_nothing略有增加,然后......我的电脑填满了它的 RAM。 -
@Matteo T. 我试图设置“什么都不做”,以便将与将数组传递给子进程相关的任何开销都考虑在内(注意我将最后一个条目放在队列中)。因此,除非 Python 解释器真的很聪明,并且认识到我在
do_nothing中根本没有使用x,否则我看不出酸洗成本是如何反映的。 -
你能试试
q.put(x[int(numpy.random.uniform(0,len(x)-1))])代替q.put(x[-1])吗?这对 CPU 来说仍然不算什么,但 python 永远不会预测它;) -
刚刚尝试过,我得到了基本相同的结果。
标签: python numpy multiprocessing