concurrent.futures.ThreadPoolExecutor 在 HPC 上扩展不佳

concurrent.futures.ThreadPoolExecutor scaling poorly on HPC

我刚开始使用 HPC,对代码并行化有一些疑问。我有一些 python 代码,我已经使用多线程成功地并行化了这些代码,这在我的个人机器和服务器上运行良好。但是,我刚刚在我的大学获得了 HPC 资源。我使用的一般代码部分如下所示:

# Iterate over weathers
    print(f'Number of CPU: {os.cpu_count()}')
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = [executor.submit(run_weather, i) for i in range(len(weather_list))]

        for f in concurrent.futures.as_completed(futures):
            results.append(f.result())

在我的个人计算机上,当 运行 两种天气(我并行化的项目)时,我得到以下结果:

Number of CPU: 8
done in 29.645377159118652 seconds

在具有 32 个内核的 1 个节点上 运行 时,在 HPC 上,我得到以下结果:

Number of CPU: 32
done in 86.95256996154785 seconds

所以它 运行 几乎是它的两倍 - 就像它是 运行 连续地处理相同的开销。我也尝试将代码切换到 ProcessPoolExecutor,它比 ThreadPoolExecutor 慢得多。我假设这一定与数据传输有关(我已经看到多个节点上的多处理试图传递整个程序、包和所有),但正如我所说,我对 HPC 和我提供的 wiki 很陌生大学还有很多不足之处。

添加线程只会减慢 CPU 绑定程序的执行速度(因为在获取和释放锁上浪费了太多时间)。与其他一些编程语言不同,在 Python 中,线程受全局解释器锁 (GIL) 的约束,因此您可能不会看到与在其他语言中看到的相同的行为。

作为 Python 的经验法则:多个 进程 有利于 CPU 绑定工作并将工作负载分散到多个内核。线程非常适合并发 IO 之类的事情。 线程不会加速任何类型的计算工作。

此外,运行 n 个线程数等于 CPU 个线程可能对您的程序没有意义。特别是因为无论如何,线程都不会跨 CPU 个核心展开工作。更多的线程会增加获取和释放 GIL 的开销,这就是为什么您使用的线程越多,执行时间就越长的原因。

后续步骤:

  1. 确定线程是否完全加快了程序的执行速度。

尝试 运行 单线程代码,然后使用少量线程重试。它会加速吗?

很有可能,您会发现线程在这里根本帮不了您。

  1. 探索多处理而不是线程。 IE。你应该使用 ProcessPoolExecutor 而不是线程池。

请记住,进程和线程一样有自己的注意事项。

这里有一个很有用的 reference 可以了解更多。