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 的开销,这就是为什么您使用的线程越多,执行时间就越长的原因。
后续步骤:
- 确定线程是否完全加快了程序的执行速度。
尝试 运行 单线程代码,然后使用少量线程重试。它会加速吗?
很有可能,您会发现线程在这里根本帮不了您。
- 探索多处理而不是线程。 IE。你应该使用
ProcessPoolExecutor
而不是线程池。
请记住,进程和线程一样有自己的注意事项。
这里有一个很有用的 reference 可以了解更多。
我刚开始使用 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 的开销,这就是为什么您使用的线程越多,执行时间就越长的原因。
后续步骤:
- 确定线程是否完全加快了程序的执行速度。
尝试 运行 单线程代码,然后使用少量线程重试。它会加速吗?
很有可能,您会发现线程在这里根本帮不了您。
- 探索多处理而不是线程。 IE。你应该使用
ProcessPoolExecutor
而不是线程池。
请记住,进程和线程一样有自己的注意事项。
这里有一个很有用的 reference 可以了解更多。