如何找到multiprocessing.Pool
个实例的最佳块大小?
之前我用过这个来创建n
数独对象的生成器:
processes = multiprocessing.cpu_count()
worker_pool = multiprocessing.Pool(processes)
sudokus = worker_pool.imap_unordered(create_sudoku, range(n), n // processes + 1)
要测量时间,我在上面的代码段之前使用time.time()
,然后按照描述初始化池,然后将生成器转换为列表(list(sudokus)
)以触发生成项目(仅对于时间测量,我知道这在最终程序中是无意义的),然后我再次使用time.time()
并输出差异。
我观察到n // processes + 1
的块大小导致每个对象大约 0.425 ms 的时间。但我也观察到CPU只在整个过程的前半部分完全加载,最终使用率降至25%(在具有2核和超线程的i3上)。
如果我使用较小的int(l // (processes**2) + 1)
块大小,我会得到大约 0.355 ms 的时间,并且CPU负载分布更好。它只有一些小的尖峰到ca. 75%,但在处理时间的长时间内保持高位,然后降至25%。
是否有更好的公式来计算块大小或更好的方法来使用CPU最有效?请帮助我提高这个多处理池的有效性。
答案 0 :(得分:10)
This answer提供了高级概述。
进入详细状态时,每个工作人员一次被发送一大块chunksize
个任务进行处理。每次工作人员完成该块时,都需要通过某种类型的进程间通信(IPC)请求更多输入,例如queue.Queue
。每个IPC请求都需要系统调用;由于上下文切换,它的成本为anywhere in the range of 1-10 μs,假设为10μs。由于共享缓存,上下文切换可能会(在有限程度上)损害所有核心。所以极其悲观地让我们估算出100μs的IPC请求的最大可能成本。
你希望IPC开销不重要,比方说< 1%。如果我的数字正确,您可以通过使块处理时间> 10 ms来确保。因此,如果每个任务需要1μs来处理,那么您至少需要chunksize
10000
。
不使chunksize
任意大的主要原因是,在执行结束时,其中一个工作人员可能仍在运行而其他人都已完成 - 显然不必要地增加完成时间。我想在大多数情况下延迟10毫秒并不是什么大问题,所以我建议定位10毫秒的块处理时间似乎是安全的。
大chunksize
可能导致问题的另一个原因是准备输入可能需要时间,同时浪费工人的能力。据推测,输入准备比处理更快(否则它应该并行化,使用像RxPY这样的东西)。所以再次针对~10 ms的处理时间似乎是安全的(假设您不介意启动延迟小于10毫秒)。
注意:对于现代Linux / Windows上的非实时进程,上下文切换每隔约1-20毫秒左右发生一次 - 除非该进程事先进行系统调用。因此,没有系统调用,上下文切换的开销不超过1%。由于IPC的原因,你所创造的开销也是如此。
答案 1 :(得分:0)
没有什么能取代实际的时间测量。我不打扰公式并尝试使用常量,例如1,10,100,1000,10000,看看哪种情况最适合你的情况。