Python 3 多处理:最佳块大小
Python 3 multiprocessing: optimal chunk size
如何找到 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 毫秒。但我也观察到 CPU 仅在进程的前半部分完全加载,最后使用率下降到 25%(在具有 2 核和超线程的 i3 上)。
如果我改用较小的块大小 int(l // (processes**2) + 1)
,我得到的时间约为 0.355 毫秒 ,并且 CPU 负载分布得更好.它只是有一些小的尖峰,低至 ca。 75%,但在下降到 25% 之前,在处理时间的较长时间内保持较高水平。
是否有更好的公式来计算块大小或使用 CPU 最有效的其他更好的方法?请帮我提高这个多处理池的效率。
没有任何东西可以取代实际的时间测量值。我不会为公式而烦恼,而是尝试使用诸如 1、10、100、1000、10000 之类的常数,看看哪种方法最适合您的情况。
提供高级概述。
进入细节,每个工作人员一次发送一大块 chunksize
任务进行处理。每次工作人员完成该块时,它都需要通过某种类型的进程间通信 (IPC) 请求更多输入,例如 queue.Queue
。每个IPC请求都需要一个系统调用;由于上下文切换,它花费 anywhere in the range of 1-10 μs,比方说 10 微秒。由于共享缓存,上下文切换可能会(在一定程度上)伤害所有内核。所以非常悲观地让我们估计一个IPC请求在100μs时的最大可能成本。
您希望 IPC 开销无关紧要,比方说 <1%。如果我的数字是正确的,您可以通过使块处理时间 >10 毫秒来确保这一点。因此,如果每个任务需要 1 微秒来处理,您需要至少 10000
.
的 chunksize
不让 chunksize
任意大的主要原因是在执行的最后,其中一个工人可能仍然 运行 而其他人都已经完成 - 显然不必要地增加完成时间。我想在大多数情况下,10 毫秒的延迟不是什么大问题,所以我建议将块处理时间定为 10 毫秒似乎是安全的。
另一个大的 chunksize
可能导致问题的原因是准备输入可能需要时间,同时浪费工人的能力。大概输入准备比处理快(否则它也应该并行化,使用类似 RxPY 的东西)。因此再次以 ~10 毫秒的处理时间为目标似乎是安全的(假设您不介意低于 10 毫秒的启动延迟)。
注意:对于现代 Linux/Windows 上的非实时进程,上下文切换每 ~1-20 毫秒左右发生一次 - 当然除非进程更早地进行系统调用。因此,在没有系统调用的情况下,上下文切换的开销不超过 ~1%。除此以外,您因 IPC 而产生的任何开销都是如此。
如何找到 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 毫秒。但我也观察到 CPU 仅在进程的前半部分完全加载,最后使用率下降到 25%(在具有 2 核和超线程的 i3 上)。
如果我改用较小的块大小 int(l // (processes**2) + 1)
,我得到的时间约为 0.355 毫秒 ,并且 CPU 负载分布得更好.它只是有一些小的尖峰,低至 ca。 75%,但在下降到 25% 之前,在处理时间的较长时间内保持较高水平。
是否有更好的公式来计算块大小或使用 CPU 最有效的其他更好的方法?请帮我提高这个多处理池的效率。
没有任何东西可以取代实际的时间测量值。我不会为公式而烦恼,而是尝试使用诸如 1、10、100、1000、10000 之类的常数,看看哪种方法最适合您的情况。
进入细节,每个工作人员一次发送一大块 chunksize
任务进行处理。每次工作人员完成该块时,它都需要通过某种类型的进程间通信 (IPC) 请求更多输入,例如 queue.Queue
。每个IPC请求都需要一个系统调用;由于上下文切换,它花费 anywhere in the range of 1-10 μs,比方说 10 微秒。由于共享缓存,上下文切换可能会(在一定程度上)伤害所有内核。所以非常悲观地让我们估计一个IPC请求在100μs时的最大可能成本。
您希望 IPC 开销无关紧要,比方说 <1%。如果我的数字是正确的,您可以通过使块处理时间 >10 毫秒来确保这一点。因此,如果每个任务需要 1 微秒来处理,您需要至少 10000
.
chunksize
不让 chunksize
任意大的主要原因是在执行的最后,其中一个工人可能仍然 运行 而其他人都已经完成 - 显然不必要地增加完成时间。我想在大多数情况下,10 毫秒的延迟不是什么大问题,所以我建议将块处理时间定为 10 毫秒似乎是安全的。
另一个大的 chunksize
可能导致问题的原因是准备输入可能需要时间,同时浪费工人的能力。大概输入准备比处理快(否则它也应该并行化,使用类似 RxPY 的东西)。因此再次以 ~10 毫秒的处理时间为目标似乎是安全的(假设您不介意低于 10 毫秒的启动延迟)。
注意:对于现代 Linux/Windows 上的非实时进程,上下文切换每 ~1-20 毫秒左右发生一次 - 当然除非进程更早地进行系统调用。因此,在没有系统调用的情况下,上下文切换的开销不超过 ~1%。除此以外,您因 IPC 而产生的任何开销都是如此。