为什么在 Linux 上的 python 3 中,“多进程”版本比单进程版本花费的时间更长?

Why does a `multiprocessing` version take longer than a single process version in python 3 on Linux?

我正在尝试将一个函数应用于大量 range 个数字 - 而我使用 multiprocessing 中的池的版本比我估计的 [=] 需要更长的时间才能完成38=] 版本 -

这是我的代码有问题吗?还是Python?或者 Linux?

我使用的函数是is_solution定义如下-

as_ten_digit_string = lambda x: f"0000000000{x}"[-10:]

def sum_of_digits(nstr):
    return sum([int(_) for _ in list(nstr)])

def is_solution(x):
    return sum_of_digits(as_ten_digit_string(x)) == 10

当我 运行 is_solution 处理一百万个数字时 - 大约需要 2 秒

In [13]: %timeit [is_solution(x) for x in range(1_000_000)]                                                                                                        
1.9 s ± 18.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

基于此 - 对于约 100 亿个数字 - 应该需要大约 20,000 秒或大约 6 小时。但是 multiprocessing 版本即使在 9 小时后也没有结束。

我正在使用这样的多处理模块 -

from multiprocessing import Pool
with Pool(processes=24) as p:
    for solution in p.imap_unordered(is_solution, range(1_000_000_000, 9_999_999_999)):
        if solution:
            print(solution)

我使用的 python 版本是 linux 上的 3.8

我不知道这是否相关 - 当我 运行 linux 中的 top 命令时 - 我看到当我的主程序有 运行 ~200 分钟 - 我的每个工作进程都有大约 20 分钟的 CPU 时间。

def solution(n, sum):
    """Generates numbers of n digits with the given total sum"""
    if n == 1 and sum < 10:
        yield str(sum)
        return
    if n < 1 or (sum > 9 and n < 2):
        return 
    if sum == 0:
        yield "0" * n
        return
    for digit in range(min(sum + 1,  10)):
        for s in solution(n - 1, sum - digit):
            yield str(digit) + s        

# Print all 4-digits numbers with total sum 10
for s in solution(4, 10):
    print(s)

# Print all 4-digits numbers with total sum 10, not starting with zero
for digit in range(1, 10):
    for s in solution(3, 10 - digit):
        print(str(digit) + s)

多重处理不是免费的。如果您有 X cpu 个核心,那么生成超过 X 个进程最终将导致性能下降。如果您的进程执行 I/O,那么即使生成 10*X 进程也可能没问题。因为他们不紧张 cpu。但是,如果您的进程进行计算和内存操作,那么 X 之上的任何进程可能只会降低性能。在评论中你说你有 4 个核心,所以你应该设置 Pool(processes=4)。您也可以尝试不同的值。 Multiprocessing很难,可能5个甚至8个进程还是会提高性能的。但是极有可能超过 4 个 cpu 内核的 24 个进程只会损害性能。

您可以做的另一件事是将数据批量发送到子进程。目前,您一个接一个地发送数据,并且由于您的计算速度很快(对于单个数据点),因此进程间通信可能占总执行时间的大部分。这是您在单进程场景​​中不付出但在多进程场景中总是付出的代价。要尽量减少其影响,请使用 imap_unorderedchunksize 参数。

最后,尝试重新实现您的算法以避免暴力破解。正如@Alex 所建议的。