如何克服 python 多处理中的开销?

Posted

技术标签:

【中文标题】如何克服 python 多处理中的开销?【英文标题】:How to overcome overhead in python multiprocessing? 【发布时间】:2021-10-23 19:38:07 【问题描述】:

我无法在 python 中获得多处理的好处。基本上,计算时间会随着每个额外添加的核心而增加。所以我的猜测是这是由于开销,但我不确定我到底做错了什么以及如何改进/克服它。我真正的问题有点复杂,但我准备了一个更简单的例子来阐明我的问题。

简要说明:

我有一个相互独立的对象列表。对于每个对象,我需要调用一个函数,该函数将其他对象的字典作为输入。然后它对初始列表中的每个对象进行多次评估。所以它基本上是一个循环中的一个循环。

下面是一个非常简化版本的问题的代码

import time
import multiprocessing as mp
import numpy as np

def test_fun(iter_nr, mp_dict, i, return_dict):
    dumm = 0
    for j in range(iter_nr):
        
        for val in mp_dict.values():
            dumm += val
            
    return_dict[i] = dumm

    
manager = mp.Manager()
return_dict = manager.dict()
mp_dict = manager.dict()
for i in range(100):
    mp_dict[str(i)] = 1
    

nproc = [2,4,6,8,10,12,16,20]
nr_iter = 2*4*6*8*10
jobs = []

print('Total number of iterations: ', nr_iter)

if __name__ == '__main__':
    
    for n_proc in nproc:
        
        nr_iter_array = (nr_iter / n_proc ) * np.ones(n_proc)

        print('Nr CPUs: ', n_proc)
        print('Nr iterations per process: ', int(nr_iter_array[0]))

        start_time = time.time()

        for i in range(n_proc):

            p = mp.Process(target = test_fun, args = (int(nr_iter_array[i]), mp_dict, i, return_dict))
            p.start()
            jobs += [p]

        for job in jobs:
            job.join()

        end_time = time.time()
        print(round(end_time - start_time, 3), 'sec')

这是输出

Total number of iterations:  3840
Nr CPUs:  2
Nr iterations per process:  1920
0.661 sec
Nr CPUs:  4
Nr iterations per process:  960
1.385 sec
Nr CPUs:  6
Nr iterations per process:  640
1.674 sec
Nr CPUs:  8
Nr iterations per process:  480
1.524 sec
Nr CPUs:  10
Nr iterations per process:  384
1.992 sec
Nr CPUs:  12
Nr iterations per process:  320
2.072 sec
Nr CPUs:  16
Nr iterations per process:  240
2.186 sec
Nr CPUs:  20
Nr iterations per process:  192
2.607 sec

如您所见,计算时间随着内核数量的增加而增加。这不是我所期望的。有谁知道这里发生了什么以及如何克服这个问题?

【问题讨论】:

工作负载越小,进程数越多,开销就越大。作为一般建议,确保工作线程有一些工作要做(>100 毫秒,越多越好),并且生成的进程不超过操作系统可用 CPU 数量的 2 倍。 CPU 的数量在这里不是问题。有 24 个可用。每个工人的工作也不应该成为问题,我需要为列表中的每个对象运行的计算非常繁重。 你的环境是什么?这可能是这里起作用的因素之一。 Linux?苹果?窗户? 环境是Linux。 【参考方案1】:

这是一个进程创建开销的例子。您的任务太小,无法从多处理中受益。

当我运行你的代码时,我得到了和你一样的结果。但是,当我开始调整时

for i in range(100):
    mp_dict[str(i)] = 1

这部分并将范围更改为 1000,有一些多处理优势(我在内核数量有限的笔记本电脑上运行,您的结果可能会有所不同)

Total number of iterations:  3840
Nr CPUs:  2
Nr iterations per process:  1920
0.237 sec
Nr CPUs:  4
Nr iterations per process:  960
0.216 sec
Nr CPUs:  6
Nr iterations per process:  640
0.221 sec
Nr CPUs:  8
Nr iterations per process:  480
0.224 sec
Nr CPUs:  10
Nr iterations per process:  384
0.233 sec
Nr CPUs:  12
Nr iterations per process:  320
0.231 sec
Nr CPUs:  16
Nr iterations per process:  240
0.243 sec
Nr CPUs:  20
Nr iterations per process:  192
0.255 sec

当我把它改成10000时,改进的结果又出现了

Total number of iterations:  3840
Nr CPUs:  2
Nr iterations per process:  1920
1.578 sec
Nr CPUs:  4
Nr iterations per process:  960
1.063 sec
Nr CPUs:  6
Nr iterations per process:  640
1.076 sec
Nr CPUs:  8
Nr iterations per process:  480
1.083 sec
Nr CPUs:  10
Nr iterations per process:  384
1.098 sec
Nr CPUs:  12
Nr iterations per process:  320
1.08 sec
Nr CPUs:  16
Nr iterations per process:  240
1.099 sec
Nr CPUs:  20
Nr iterations per process:  192
1.122 sec

我没有尝试但可能会再次更改数字的调整是用来自多处理的池替换您的自我管理进程。这个例子可能很好地与池一起工作。一个池只启动 N 个进程一次,然后继续使用它们,并在工作人员空闲时立即向它们发送更多工作。您生成并杀死大量进程,而池只执行一次。

不过,这不是灵丹妙药。我最喜欢的多处理问题是数据传输。多处理和池在队列中执行此操作,这非常慢。管理器使用相同的队列并且速度也很慢。您传入或传出工作人员的数据越多,您将花费更多的时间来执行此操作。通常最好重新设计代码,以便它在工作人员中完成任务,而不是发送大量输入/输出。

无论如何,结论总是取决于您的情况。多处理并不总能提高性能,即使确实如此,改进也可能是适度的。它需要像您所做的那样进行一些评估,以找出最佳位置。

这有点没有答案,但对于 cmets 来说太长了。

【讨论】:

以上是关于如何克服 python 多处理中的开销?的主要内容,如果未能解决你的问题,请参考以下文章

首次运行时的 Python 多处理开销

python中的IO多路复用

Linux 操作系统原理 — NUMA 架构中的多线程调度开销与性能优化

Linux 操作系统原理 — 进程管理 — NUMA 架构中的多线程调度开销与性能优化

克服 Hibernate 中的延迟加载问题,多对多关系

如何在 Python 中的多处理期间访问全局变量 [重复]