有没有人注意观察过, Python 多进程执行同一程序速度比单进程执行慢很多,原因是什么?

2021-03-18 06:58:35 +08:00
 LeeReamond

如题,我在测试 ctypes 释放 GIL 的过程中发现这个问题,即使使用 c 代码将 GIL 释放,多线程并行的效率并不是比如我有 N 个线程那么程序的运算能力就变成 N 倍。即使线程之间完全没有资源竞争问题,这个是令我很意外的一个点。

我觉得可能的原因是线程之间始终要进行一些状态同步,那 OK 我使用多进程总归是完全隔离了吧,结果测试结果没有太大变化,令人大跌眼镜。

我理解上,进程互相之间完全独立,如果你的物理计算资源足够(比如我使用的 CPU 是 8 核心 16 线程的),那么你运行 8 个独立的进程,他们应该是互相完全独立,速度互不干扰的,但实验结果并非如此,请问一下 v 友们之中有没有大佬能解释一下原因,谢谢。

=====

测试代码如下,因为我无法上传 DLL,使用递归菲波那切数列模拟 CPU 密集型任务。这会使多线程执行时间线性增长,但理论不应影响到多进程。另外以下实验代码中使用子进程的方式,我担心可能是子进程状态同步导致的效率损失,但实际手动在 shell 中启动多个不同进程,实验结果没有区别。

以下使用的进程池 /线程池都经过了预激。

from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
import time

def pre_activate(times):
    time.sleep(times)

def execution():
	
    def fib(n):
        if n<=1:
            return 1
        return fib(n-1) + fib(n-2)

    for i in range(20):
        fib(30)

if __name__ == "__main__":

    core_num = 8
    st_time = time.time()
    execution()
    single_execute_time = time.time() - st_time
    print(f"Single thread execute time: {round(single_execute_time,4)} s")

    with ThreadPoolExecutor(max_workers=core_num) as executor:
        # pre-activate {core_num} threads in threadpoolexecutor
        pre_task = [executor.submit(pre_activate, times) \
            for times in [0.5 for _ in range(core_num)]]
        for future in as_completed(pre_task):future.result()

        st_time = time.time()
        tasks = [executor.submit(execution) for _ in range(core_num)]
        for future in as_completed(tasks):future.result()
        print(f"Multi thread execute time: {round(time.time() - st_time,4)} s",
              f", speedup: {round(core_num * single_execute_time / (time.time() - st_time),2)} x")

    with ProcessPoolExecutor(max_workers=core_num) as executor:
        #
        pre_task = [executor.submit(pre_activate, times) 
            for times in [0.5 for _ in range(core_num)]]
        for future in as_completed(pre_task):future.result()

        st_time = time.time()
        tasks = [executor.submit(execution) for _ in range(core_num)]
        for future in as_completed(tasks):future.result()
        print(f"Multi Process execute time: {round(time.time() - st_time,4)} s",
              f", speedup: {round(core_num * single_execute_time / (time.time() - st_time),2)} x")

我的本地执行结果是:

Single thread execute time: 4.117 s
Multi thread execute time: 32.888 s , speedup: 1.0 x
Multi Process execute time: 12.1088 s , speedup: 2.72 x

无论更换哪些 CPU 密集型任务,speedup 几乎很难提升到 3 倍以上,即使使用 8 核心并行计算,为什么?

这个结果同时让我想起一些以前的跑分经验,比如进入异步时代以后使用 gunicorn 单线程部署一个 web 服务通常 echo 可以做到每秒钟两万次以上,但使用 prefork 的多进程,也不过将这个数值提升 2-2.5 倍,并不能提升很多,以前没有细究,现在觉得不太对

2029 次点击
所在节点    Python
42 条回复
binux
2021-03-18 13:02:59 +08:00
进程间通信开销没有那么大
多核性能并不是单核性能 xN,你去看 CPU benchmark 就能注意到
你代码统计的是最慢的一个进程用时,试试分别计时再加起来?
LeeReamond
2021-03-18 13:46:01 +08:00
@binux 提这个问题主要是我 8 核才提升 2 倍性能,比较诡异所以来问一下,看大家的跑分感觉可能是我硬件问题,不是 python 或者调用方式有问题。物理 8 核心分配 8 线程,资源足够的情况下执行顺序先后完成时间差别不大,属于比较粗略的计算方式,因为不需要进程间通讯。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/762657

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX