为什么多进程池比 for 循环慢?
why is multiprocess Pool slower than a for loop?
from multiprocessing import Pool
def op1(data):
return [data[elem] + 1 for elem in range(len(data))]
data = [[elem for elem in range(20)] for elem in range(500000)]
import time
start_time = time.time()
re = []
for data_ in data:
re.append(op1(data_))
print('--- %s seconds ---' % (time.time() - start_time))
start_time = time.time()
pool = Pool(processes=4)
data = pool.map(op1, data)
print('--- %s seconds ---' % (time.time() - start_time))
与使用 for 循环相比,我使用 pool 的 运行 时间要慢得多。但是 pool 不是应该使用 4 个处理器来并行计算吗?
简短回答:是,这些操作通常在可用核心(的子集)上完成。但是通信开销很大。 在您的示例中,与开销相比工作量太小。
如果您构建一个池,将构建多个 worker。如果您随后指示给定输入 map
。发生以下情况:
- 数据将拆分:每个工人得到大致公平的份额;
- 数据将传达给工人;
- 每个工人将处理他们分担的工作;
- 结果传回进程;和
- 主进程将结果组合在一起。
现在拆分、通信和连接数据都是由主进程执行的进程。这些 无法并行化 。由于操作速度很快(O(n),输入大小为 n),开销具有 相同的时间复杂度.
如此复杂,即使您有数百万个内核,也不会有太大区别,因为传达列表可能已经比计算结果更昂贵。
这就是为什么您应该并行化计算量大的任务。不是简单的任务。与通信量相比,处理量应该大。
在您的示例中,工作微不足道:您将所有元素加 1。然而,序列化并不那么简单:您必须对发送给工作人员的列表进行编码。
您的代码有几个潜在的问题点,但主要是它太简单了。
multiprocessing
模块通过创建不同的进程并在它们之间进行通信来工作。对于每一个创建的进程,你都要支付操作系统的进程启动成本,以及python的启动成本。这些成本可高可低,但无论如何都不为零。
支付这些启动费用后,您就可以 pool.map
工作人员在所有流程中发挥作用。这基本上将 1 加到几个数字上。正如您的测试所证明的那样,这不是一个很大的负载。
更糟糕的是,您使用的是隐式排序的 .map()
(与 .imap_unordered()
相比),因此同步正在进行 - 为各种 CPU 核心留下更少的自由给你速度。
如果这里有问题,那就是 "design of experiment" 问题 - 您还没有创建一个足够困难的问题 multiprocessing
来帮助您。
正如其他人所指出的,您为促进多处理而付出的开销超过了通过跨多个内核并行化所节省的时间。换句话说,您的函数 op1()
不需要足够的 CPU 资源来查看并行化带来的性能提升。
在 multiprocessing.Pool
class 中,听到的大部分内容是在数据在父进程(创建池)之间穿梭之前对数据进行序列化和反序列化 和子进程 "worker" 进程。
This blog post 更详细地探讨了使用 multiprocessing.Pool
模块时 pickling
( 序列化 )的开销有多大。
from multiprocessing import Pool
def op1(data):
return [data[elem] + 1 for elem in range(len(data))]
data = [[elem for elem in range(20)] for elem in range(500000)]
import time
start_time = time.time()
re = []
for data_ in data:
re.append(op1(data_))
print('--- %s seconds ---' % (time.time() - start_time))
start_time = time.time()
pool = Pool(processes=4)
data = pool.map(op1, data)
print('--- %s seconds ---' % (time.time() - start_time))
与使用 for 循环相比,我使用 pool 的 运行 时间要慢得多。但是 pool 不是应该使用 4 个处理器来并行计算吗?
简短回答:是,这些操作通常在可用核心(的子集)上完成。但是通信开销很大。 在您的示例中,与开销相比工作量太小。
如果您构建一个池,将构建多个 worker。如果您随后指示给定输入 map
。发生以下情况:
- 数据将拆分:每个工人得到大致公平的份额;
- 数据将传达给工人;
- 每个工人将处理他们分担的工作;
- 结果传回进程;和
- 主进程将结果组合在一起。
现在拆分、通信和连接数据都是由主进程执行的进程。这些 无法并行化 。由于操作速度很快(O(n),输入大小为 n),开销具有 相同的时间复杂度.
如此复杂,即使您有数百万个内核,也不会有太大区别,因为传达列表可能已经比计算结果更昂贵。
这就是为什么您应该并行化计算量大的任务。不是简单的任务。与通信量相比,处理量应该大。
在您的示例中,工作微不足道:您将所有元素加 1。然而,序列化并不那么简单:您必须对发送给工作人员的列表进行编码。
您的代码有几个潜在的问题点,但主要是它太简单了。
multiprocessing
模块通过创建不同的进程并在它们之间进行通信来工作。对于每一个创建的进程,你都要支付操作系统的进程启动成本,以及python的启动成本。这些成本可高可低,但无论如何都不为零。
支付这些启动费用后,您就可以 pool.map
工作人员在所有流程中发挥作用。这基本上将 1 加到几个数字上。正如您的测试所证明的那样,这不是一个很大的负载。
更糟糕的是,您使用的是隐式排序的 .map()
(与 .imap_unordered()
相比),因此同步正在进行 - 为各种 CPU 核心留下更少的自由给你速度。
如果这里有问题,那就是 "design of experiment" 问题 - 您还没有创建一个足够困难的问题 multiprocessing
来帮助您。
正如其他人所指出的,您为促进多处理而付出的开销超过了通过跨多个内核并行化所节省的时间。换句话说,您的函数 op1()
不需要足够的 CPU 资源来查看并行化带来的性能提升。
在 multiprocessing.Pool
class 中,听到的大部分内容是在数据在父进程(创建池)之间穿梭之前对数据进行序列化和反序列化 和子进程 "worker" 进程。
This blog post 更详细地探讨了使用 multiprocessing.Pool
模块时 pickling
( 序列化 )的开销有多大。