使用比核心更多的工作进程

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 秒的速度。