【问题标题】:Why does a `multiprocessing` version take longer than a single process version in python 3 on Linux?为什么在 Linux 上的 python 3 中,“多处理”版本比单进程版本花费更长的时间?
【发布时间】:2020-04-06 08:21:06
【问题描述】:

我正在尝试将函数应用于大量的数字 range - 我使用来自 multiprocessing 的池的版本比我估计的“单进程”版本花费的时间要长得多 -

这是我的代码有问题吗?还是蟒蛇?还是 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版本是3.8在linux上。

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

【问题讨论】:

  • 您只需生成所有数字之和 == 10 的数字的列表,您无需为此测试每个连续整数。
  • @Alex - 我很好奇如何。谢谢。但更重要的是,我希望了解我的代码为什么会这样运行——我不认为我的 is_solution 函数是我看到的原因,或者是吗?
  • 如何让多处理代码运行?无论我尝试什么,我都会收到“无法腌制 lambda”错误。 mac 和 ubuntu,python 3.8。
  • @Mortz 你有多少核心?超过核心数量的每个进程都会降低性能。假设这些进程执行像您这样的 CPU 密集型任务。也可能是进程之间发送和接收数据的实际时间比实际处理时间长。要解决这个问题,您可以分批发送数据,而不是一个一个地发送。这意味着您可以将 [start, end] 对而不是单个值发送到 imap_unordered 并修复您的 is_solution 以接受范围。
  • @Mortz 太好了,完美。只要记住将工作人员的数量保持在 cpus 的数量左右即可。 24 太多了。

标签: python-3.x linux multiprocessing python-multiprocessing


【解决方案1】:

多处理不是免费的。如果您有 X 个 cpu 内核,那么产生超过 X 个进程最终将导致性能下降。如果您的进程执行 I/O,那么即使生成 10*X 进程也可以。因为它们不会对 CPU 造成压力。但是,如果您的进程进行计算和内存操作,那么任何高于 X 的进程都可能只会降低性能。在 cmets 你说你有 4 个核心,所以你应该设置Pool(processes=4)。您也可以尝试不同的值。多处理很难,可能5个甚至8个进程仍然会提高性能。但极有可能 4 个 cpu 内核上的 24 个进程只会损害性能。

您可以做的另一件事是将数据批量发送到子进程。目前,您一个接一个地发送数据,并且由于您的计算速度很快(对于单个数据点),因此进程间通信可能会主导总执行时间。这是您在单进程方案中不支付的价格,但在多处理时您总是要付出代价。为了尽量减少它的影响,请使用imap_unorderedchunksize 参数。

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

【讨论】:

    【解决方案2】:
    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)
    

    【讨论】:

    • 这太酷了 - 我计时了,这需要大约 5 毫秒,而我的蛮力方法需要 2 秒。
    • 在我的电脑上解决您原来的问题 - 10 位数字 - 需要 0.1 秒
    猜你喜欢
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-14
    • 1970-01-01
    • 1970-01-01
    • 2018-08-18
    相关资源
    最近更新 更多