使用比核心更多的工作进程
Using more worker processes than there are cores
来自 PYMOTW 的 This 示例给出了使用 multiprocessing.Pool()
的示例,其中传递的 processes
参数(工作进程数)是机器上核心数的两倍。
pool_size = multiprocessing.cpu_count() * 2
(否则 class 将默认为 cpu_count()
。)
这有什么依据吗?创造比核心更多的工人有什么影响?是否有理由这样做,或者它是否会在错误的方向上施加额外的开销?我很好奇为什么它会始终包含在我认为是信誉良好的网站的示例中。
在初始测试中,它实际上似乎放慢了速度:
$ python -m timeit -n 25 -r 3 'import double_cpus; double_cpus.main()'
25 loops, best of 3: 266 msec per loop
$ python -m timeit -n 25 -r 3 'import default_cpus; default_cpus.main()'
25 loops, best of 3: 226 msec per loop
double_cpus.py
:
import multiprocessing
def do_calculation(n):
for i in range(n):
i ** 2
def main():
with multiprocessing.Pool(
processes=multiprocessing.cpu_count() * 2,
maxtasksperchild=2,
) as pool:
pool.map(do_calculation, range(1000))
default_cpus.py
:
def main():
# `processes` will default to cpu_count()
with multiprocessing.Pool(
maxtasksperchild=2,
) as pool:
pool.map(do_calculation, range(1000))
如果您的任务是 I/O 绑定的(例如等待数据库、网络服务),那么创建比处理器更多的线程实际上会增加您的吞吐量。
这是因为当您的线程正在等待 I/O 时,处理器实际上可以在其他线程上工作。
如果您有一项 CPU 繁重的任务,那么更多的处理器实际上会减慢它的速度。
如果您的工作不是 纯粹 cpu,但也涉及一些 I/O.
,那么这样做是有意义的
您的示例中的计算对于合理的基准测试来说也太短了,首先创建更多进程的开销占主导地位。
我修改了你的计算,让它在 10M 的 运行ge 上迭代,同时计算一个 if 条件,让它小睡一下,以防它计算为 True
,这发生了 n_sleep
次。
这样一来,总睡眠 sleep_sec_total
就可以注入到计算中。
# default_cpus.py
import time
import multiprocessing
def do_calculation(iterations, n_sleep, sleep_sec):
for i in range(iterations):
if i % (iterations / n_sleep) == 0:
time.sleep(sleep_sec)
def main(sleep_sec_total):
iterations = int(10e6)
n_sleep = 100
sleep_sec = sleep_sec_total / n_sleep
tasks = [(iterations, n_sleep, sleep_sec)] * 20
with multiprocessing.Pool(
maxtasksperchild=2,
) as pool:
pool.starmap(do_calculation, tasks)
# double_cpus.py
...
def main(sleep_sec_total):
iterations = int(10e6)
n_sleep = 100
sleep_sec = sleep_sec_total / n_sleep
tasks = [(iterations, n_sleep, sleep_sec)] * 20
with multiprocessing.Pool(
processes=multiprocessing.cpu_count() * 2,
maxtasksperchild=2,
) as pool:
pool.starmap(do_calculation, tasks)
我 运行 基准测试具有 sleep_sec_total=0
(纯 cpu 绑定)和两个模块的 sleep_sec_total=2
。
sleep_sec_total=0
的结果:
$ python -m timeit -n 5 -r 3 'import default_cpus; default_cpus.main(0)'
5 loops, best of 3: 15.2 sec per loop
$ python -m timeit -n 5 -r 3 'import double_cpus; double_cpus.main(0)'
5 loops, best of 3: 15.2 sec per loop
给定合理的计算大小,对于纯 cpu 绑定的任务,您将观察到默认 cpu 和双 cpu 之间几乎没有区别。碰巧这两个测试的最佳时间相同。
sleep_sec_total=2
的结果:
$ python -m timeit -n 5 -r 3 'import default_cpus; default_cpus.main(2)'
5 loops, best of 3: 20.5 sec per loop
$ python -m timeit -n 5 -r 3 'import double_cpus; double_cpus.main(2)'
5 loops, best of 3: 17.7 sec per loop
现在添加 2 秒睡眠作为 I/0 的虚拟对象,画面看起来不一样了。与默认设置相比,使用双倍的进程可以加快大约 3 秒的速度。
This 示例给出了使用 multiprocessing.Pool()
的示例,其中传递的 processes
参数(工作进程数)是机器上核心数的两倍。
pool_size = multiprocessing.cpu_count() * 2
(否则 class 将默认为 cpu_count()
。)
这有什么依据吗?创造比核心更多的工人有什么影响?是否有理由这样做,或者它是否会在错误的方向上施加额外的开销?我很好奇为什么它会始终包含在我认为是信誉良好的网站的示例中。
在初始测试中,它实际上似乎放慢了速度:
$ python -m timeit -n 25 -r 3 'import double_cpus; double_cpus.main()'
25 loops, best of 3: 266 msec per loop
$ python -m timeit -n 25 -r 3 'import default_cpus; default_cpus.main()'
25 loops, best of 3: 226 msec per loop
double_cpus.py
:
import multiprocessing
def do_calculation(n):
for i in range(n):
i ** 2
def main():
with multiprocessing.Pool(
processes=multiprocessing.cpu_count() * 2,
maxtasksperchild=2,
) as pool:
pool.map(do_calculation, range(1000))
default_cpus.py
:
def main():
# `processes` will default to cpu_count()
with multiprocessing.Pool(
maxtasksperchild=2,
) as pool:
pool.map(do_calculation, range(1000))
如果您的任务是 I/O 绑定的(例如等待数据库、网络服务),那么创建比处理器更多的线程实际上会增加您的吞吐量。
这是因为当您的线程正在等待 I/O 时,处理器实际上可以在其他线程上工作。
如果您有一项 CPU 繁重的任务,那么更多的处理器实际上会减慢它的速度。
如果您的工作不是 纯粹 cpu,但也涉及一些 I/O.
,那么这样做是有意义的您的示例中的计算对于合理的基准测试来说也太短了,首先创建更多进程的开销占主导地位。
我修改了你的计算,让它在 10M 的 运行ge 上迭代,同时计算一个 if 条件,让它小睡一下,以防它计算为 True
,这发生了 n_sleep
次。
这样一来,总睡眠 sleep_sec_total
就可以注入到计算中。
# default_cpus.py
import time
import multiprocessing
def do_calculation(iterations, n_sleep, sleep_sec):
for i in range(iterations):
if i % (iterations / n_sleep) == 0:
time.sleep(sleep_sec)
def main(sleep_sec_total):
iterations = int(10e6)
n_sleep = 100
sleep_sec = sleep_sec_total / n_sleep
tasks = [(iterations, n_sleep, sleep_sec)] * 20
with multiprocessing.Pool(
maxtasksperchild=2,
) as pool:
pool.starmap(do_calculation, tasks)
# double_cpus.py
...
def main(sleep_sec_total):
iterations = int(10e6)
n_sleep = 100
sleep_sec = sleep_sec_total / n_sleep
tasks = [(iterations, n_sleep, sleep_sec)] * 20
with multiprocessing.Pool(
processes=multiprocessing.cpu_count() * 2,
maxtasksperchild=2,
) as pool:
pool.starmap(do_calculation, tasks)
我 运行 基准测试具有 sleep_sec_total=0
(纯 cpu 绑定)和两个模块的 sleep_sec_total=2
。
sleep_sec_total=0
的结果:
$ python -m timeit -n 5 -r 3 'import default_cpus; default_cpus.main(0)'
5 loops, best of 3: 15.2 sec per loop
$ python -m timeit -n 5 -r 3 'import double_cpus; double_cpus.main(0)'
5 loops, best of 3: 15.2 sec per loop
给定合理的计算大小,对于纯 cpu 绑定的任务,您将观察到默认 cpu 和双 cpu 之间几乎没有区别。碰巧这两个测试的最佳时间相同。
sleep_sec_total=2
的结果:
$ python -m timeit -n 5 -r 3 'import default_cpus; default_cpus.main(2)'
5 loops, best of 3: 20.5 sec per loop
$ python -m timeit -n 5 -r 3 'import double_cpus; double_cpus.main(2)'
5 loops, best of 3: 17.7 sec per loop
现在添加 2 秒睡眠作为 I/0 的虚拟对象,画面看起来不一样了。与默认设置相比,使用双倍的进程可以加快大约 3 秒的速度。