Python 在4个CPU上执行CPU受限任务时,20个进程中的400个线程的性能优于4个进程中的400个线程

Python 在4个CPU上执行CPU受限任务时,20个进程中的400个线程的性能优于4个进程中的400个线程,python,multithreading,performance,multiprocessing,gil,Python,Multithreading,Performance,Multiprocessing,Gil,这个问题很类似于。唯一的区别是,链接的问题是关于I/O绑定的任务,而这个问题是关于CPU绑定的任务 实验代码 下面是一段实验代码,它可以启动指定数量的工作进程,然后在每个进程中启动指定数量的工作线程,并执行计算第n个素数的任务 导入数学 导入多处理 随机输入 导入系统 导入时间 导入线程 def main(): 进程=int(sys.argv[1]) threads=int(sys.argv[2]) tasks=int(sys.argv[3]) #开始工作。 in_q=多处理。队列() 工艺工人

这个问题很类似于。唯一的区别是,链接的问题是关于I/O绑定的任务,而这个问题是关于CPU绑定的任务

实验代码 下面是一段实验代码,它可以启动指定数量的工作进程,然后在每个进程中启动指定数量的工作线程,并执行计算第n个素数的任务

导入数学
导入多处理
随机输入
导入系统
导入时间
导入线程
def main():
进程=int(sys.argv[1])
threads=int(sys.argv[2])
tasks=int(sys.argv[3])
#开始工作。
in_q=多处理。队列()
工艺工人=[]
对于范围内的(过程):
进程(目标=进程\工作进程,参数=(线程,in_q))
w、 开始()
工艺工人。附加(w)
开始时间=time.time()
#饲料工作。
对于范围内的第n个任务(1,任务+1):
投入产出(N)
#为每个要退出的线程工作程序发送sentinel。
对于范围内的(进程*线程):
投入产出(无)
#等待工人终止。
对于w在制品工人:
w、 加入
总时间=时间。时间()-开始时间
任务速度=任务/总时间
打印({:3d}x{:3d}工作者=>{:6.3f}s,{:5.1f}任务/s'
.format(进程、线程、总时间、任务速度))
def进程\u工作者(线程,in_q):
线程\工作线程=[]
对于范围内的(螺纹):
线程(target=Thread\u worker,args=(in\u q,))
w、 开始()
线程\u workers.append(w)
对于w-in-thread_工人:
w、 加入
def线程工作线程(in_q):
尽管如此:
n=in_q.get()
如果n为无:
打破
num=查找第n个素数(第n个)
#打印(个)
def查找第n个素数(第n个):
#从头开始寻找第n个素数。
如果n==0:
返回
计数=0
num=2
尽管如此:
如果是_素数(num):
计数+=1
如果计数==n:
返回数
num+=1
def是_prime(num):
对于范围内的i(2,int(math.sqrt(num))+1):
如果num%i==0:
返回错误
返回真值
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
main()
以下是我如何运行此程序:

python3 foo.py <PROCESSES> <THREADS> <TASKS>
案例1:20个进程x 20个线程 下面是一些在20个工作进程之间分布400个工作线程的试运行(即,20个工作进程中的每个进程中都有20个工作线程)

结果如下:

$python3 bar.py 20 2000
20 x 20工人=>12.702秒,157.5个任务/秒
$python3 bar.py 20 2000
20 x 20工人=>13.196秒,151.6个任务/秒
$python3 bar.py 20 2000
20 x 20工人=>12.224秒,163.6个任务/秒
$python3 bar.py 20 2000
20 x 20工人=>11.725秒,170.6个任务/秒
$python3 bar.py 20 2000
20 x 20工人=>10.813秒,185.0任务/秒
当我使用
top
命令监视CPU使用情况时,我看到每个
python3
工作进程消耗大约15%到25%的CPU

案例2:4个进程x 100个线程 现在我想我只有4个CPU。即使我启动了20个辅助进程,在物理时间的任何一点上最多只能运行4个进程。此外,由于全局解释器锁(GIL),每个进程中只有一个线程(因此最多总共4个线程)可以在物理时间的任何时间点运行

因此,我认为如果我将进程数减少到4个,并将每个进程的线程数增加到100个,这样线程总数仍然保持在400个,那么性能就不会恶化

但测试结果表明,4个进程(每个进程包含100个线程)的性能始终比20个进程(每个进程包含20个线程)的性能差

$python3 bar.py 4 100 2000
4 x 100工人=>19.840秒,100.8个任务/秒
$python3 bar.py 41000 2000
4 x 100名工人=>22.716秒,88.0个任务/秒
$python3 bar.py 41000 2000
4 x 100名工人=>20.278秒,98.6个任务/秒
$python3 bar.py 41000 2000
4 x 100名工人=>19.896秒,100.5个任务/秒
$python3 bar.py 41000 2000
4 x 100名工人=>19.876秒,100.6个任务/秒
每个
python3
工作进程的CPU使用率在50%到66%之间

案例3:1个进程x 400个线程 为了进行比较,我记录了一个事实,即案例1和案例2的性能都优于我们在一个进程中拥有全部400个线程的情况。这显然是由于全局解释器锁(GIL)

$python3 bar.py 1400 2000
1 x 400工人=>34.762秒,57.5个任务/秒
$python3 bar.py 1400 2000
1 x 400工人=>35.276秒,56.7个任务/秒
$python3 bar.py 1400 2000
1 x 400工人=>32.589秒,61.4个任务/秒
$python3 bar.py 1400 2000
1 x 400工人=>33.974秒,58.9个任务/秒
$python3 bar.py 1400 2000
1 x 400工人=>35.429秒,56.5个任务/秒
单个
python3
工作进程的CPU使用率在110%到115%之间

案例4:400个进程x 1个线程 同样,为了进行比较,这里是当有400个进程,每个进程都有一个线程时,结果的样子

$python3 bar.py 400 1 2000
400 x 1工人=>8.814秒,226.9个任务/秒
$python3 bar.py 400 1 2000
400 x 1工人=>8.631秒,231.7个任务/秒
$python3 bar.py 400 1 2000
400 x 1工人=>10.453秒,191.3个任务/秒
$python3 bar.py 400 1 2000
400 x 1工人=>8.234秒,242.9个任务/秒
$python3 bar.py 400 1 2000
400 x 1工人=>8.324秒,240.3个任务/秒
每个
python3
工作进程的CPU使用率在1%到3%之间

总结 从每个案例中选取中值结果,我们得到以下总结:

Case 1:  20 x  20 workers => 12.224 s, 163.6 tasks/s
Case 2:   4 x 100 workers => 19.896 s, 100.5 tasks/s
Case 3:   1 x 400 workers => 34.762 s,  57.5 tasks/s
Case 4: 400 x   1 workers =>  8.631 s, 231.7 tasks/s
问题: 为什么即使我只有4个CPU,20个进程x 20个线程的性能也比4个进程x 100个线程好


事实上,尽管只有4个CPU,但400个进程x 1线程的性能最好?为什么?

Python中的线程不并行执行,原因是臭名昭著的:

<