【问题标题】:Why doesn't multiprocessing pool map speed up compared to serial map?与串行映射相比,为什么多处理池映射不加快速度?
【发布时间】:2018-01-28 18:14:38
【问题描述】:

我有一个非常简单的 python 代码,我想通过并行化它来加速它。然而,无论我做什么,multiprocessing.Pool.map 都没有比标准地图获得任何好处。

我读过其他线程,人们将它与非常小的函数一起使用,这些函数不能很好地并行化并导致过多的开销,但我认为这里不应该是这种情况。

我做错了吗?

这是一个例子

#!/usr/bin/python

import numpy, time

def AddNoise(sample):
    #time.sleep(0.001)
    return sample + numpy.random.randint(0,9,sample.shape)
    #return sample + numpy.ones(sample.shape)

n=100
m=10000
start = time.time()
A = list([ numpy.random.randint(0,9,(n,n)) for i in range(m) ])
print("creating %d numpy arrays of %d x %d took %.2f seconds"%(m,n,n,time.time()-start))

for i in range(3):
    start = time.time()
    A = list(map(AddNoise, A))
    print("adding numpy arrays took %.2f seconds"%(time.time()-start))

for i in range(3):
    import multiprocessing
    start = time.time()
    with multiprocessing.Pool(processes=2) as pool:
        A = list(pool.map(AddNoise, A, chunksize=100))
    print("adding numpy arrays with multiprocessing Pool took %.2f seconds"%(time.time()-start))

for i in range(3):
    import concurrent.futures
    start = time.time()
    with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:
        A = list(executor.map(AddNoise, A))
    print("adding numpy arrays with concurrent.futures.ProcessPoolExecutor took %.2f seconds"%(time.time()-start))

这会在我的 4 核/8 线程笔记本电脑上产生以下输出,否则会处于空闲状态

$ python test-pool.py 
creating 10000 numpy arrays of 100 x 100 took 1.54 seconds
adding numpy arrays took 1.65 seconds
adding numpy arrays took 1.51 seconds
adding numpy arrays took 1.51 seconds
adding numpy arrays with multiprocessing Pool took 1.99 seconds
adding numpy arrays with multiprocessing Pool took 1.98 seconds
adding numpy arrays with multiprocessing Pool took 1.94 seconds
adding numpy arrays with concurrent.futures.ProcessPoolExecutor took 3.32 seconds
adding numpy arrays with concurrent.futures.ProcessPoolExecutor took 3.17 seconds
adding numpy arrays with concurrent.futures.ProcessPoolExecutor took 3.25 seconds

【问题讨论】:

  • 如果它可以在 numpy 中完成,那么它可能会回避 GIL,这是使用 multiprocessing 的主要原因。
  • 但标准的map 调用也不会使用更多的 100% cpu。
  • 由于 GIL,标准映射不能使用超过 1 个核心
  • @roganjosh 回答您之前的评论:在我的笔记本上,创建两个进程池的时间约为 4.5 毫秒。
  • @roganjosh,在第一次尝试使用%timeit 后,结果相同:D,我使用了几次%time

标签: python python-3.x numpy multiprocessing pool


【解决方案1】:

问题在于结果传输。考虑到通过多处理,您在子进程中创建的数组需要被传输回主进程。这是一种开销。

我检查了以这种方式修改 AddNoise 函数,它保留了计算时间,但丢弃了传输时间:

def AddNoise(sample):
   sample + numpy.random.randint(0,9,sample.shape)
   return None

【讨论】:

  • 嗯,有道理。因此,如果我理解正确,我将永远无法从多处理池中受益......除非我能以某种方式共享内存。
  • 这就是诀窍。仅当计算某些东西的工作量与通信开销相比很大时,您才能从多处理中受益。如果您使用只读对象,则可以在 linux 机器上免费使用,因为写入时复制行为。但是,对于写入或共享对象,这很棘手,并且没有标准答案是否可以。
  • @jaap 但如果这实际上代表了您要完成的任务,您应该只在 numpy 中进行(正确地,使用矢量化计算)而不进行多处理,它会更快,无论问题大小。
  • @roganjosh 是对的。当我真的需要像这样并行计算时,我会使用 cython 代码和 nogil 以及线程池。但这需要更多的努力......
  • 你的回答只解决了一半的问题。只需将AddNoise() 中的返回值更改为None,只会消除大约一半的传输。还有通过map(AddNoise, A) 调用将参数A 传递给 函数的开销。 A 很大,因此在进程之间以任一 方向传输它会显着增加开销。
猜你喜欢
  • 2017-04-09
  • 1970-01-01
  • 2019-09-18
  • 2011-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多