如何使用 python 多处理找到 运行 的理想并行进程数?
How to find ideal number of parallel processes to run with python multiprocessing?
正在尝试通过 python multiprocessing 找到 运行 的正确并行进程数。
下面的脚本是 运行 在 8 核 32 GB (Ubuntu 18.04) 机器上的。 (只有系统进程和基本用户进程 运行ning 测试了以下内容。)
使用以下内容测试了 multiprocessing.Pool
和 apply_async
:
from multiprocessing import current_process, Pool, cpu_count
from datetime import datetime
import time
num_processes = 1 # vary this
print(f"Starting at {datetime.now()}")
start = time.perf_counter()
print(f"# CPUs = {cpu_count()}") # 8
num_procs = 5 * cpu_count() # 40
def cpu_heavy_fn():
s = time.perf_counter()
print(f"{datetime.now()}: {current_process().name}")
x = 1
for i in range(1, int(1e7)):
x = x * i
x = x / i
t_taken = round(time.perf_counter() - s, 2)
return t_taken, current_process().name
pool = Pool(processes=num_processes)
multiple_results = [pool.apply_async(cpu_heavy_fn, ()) for i in range(num_procs)]
results = [res.get() for res in multiple_results]
for r in results:
print(r[0], r[1])
print(f"Done at {datetime.now()}")
print(f"Time taken = {time.perf_counter() - start}s")
结果如下:
num_processes total_time_taken
1 28.25
2 14.28
3 10.2
4 7.35
5 7.89
6 8.03
7 8.41
8 8.72
9 8.75
16 8.7
40 9.53
以下对我有意义:
- 运行一次一个进程每个进程大约需要0.7秒,所以运行ning 40应该大约需要28s,这与我们上面观察到的一致。
- 运行 一次 2 个进程应该将时间减半,这是在上面观察到的(~14s)。
- 运行 一次 4 个进程应该进一步减半时间,这在上面观察到(~7s)。
- 将并行度增加到超过内核数 (8) 应该会降低性能(由于 CPU 争用),这是观察到的(某种程度上)。
没有意义的是:
- 为什么 运行ning 8 并联的速度不是 运行ning 4 并联的两倍,即为什么不是 ~3.5s?
- 为什么运行一次并发 5 到 8 个比 运行一次并发 4 个差?有 8 个核心,但为什么整体 运行 时间更差? (当 运行ning 8 并行时,
htop
显示所有 CPU 的利用率接近 100%。当 运行ning 4 并行时,只有 4 个处于 100%这是有道理的。)
最可能的原因是您 运行 CPU 上的程序在 Intel 设备上使用 simultaneous multithreading (SMT), better known as hyper-threading。在 wiki 之后引用,对于物理上存在的每个处理器核心,操作系统寻址两个虚拟(逻辑)核心并在可能的情况下在它们之间分担工作负载。这就是这里发生的事情。
你的OS说是8核,其实是4核带SMT。任务显然是 CPU-bound,因此任何超出 physical 核心数量的增加都不会带来任何好处,只会带来多处理的开销成本。这就是为什么您会看到性能几乎呈线性增长,直到达到(物理!)最大值。核心数量 (4),然后在需要为这项非常 CPU 密集型任务共享核心时减少。
Q : "Why is running 5 to 8 in parallel at a time worse than running 4 at a time?"
好吧,有几个原因,我们将从一个静态的、最容易观察到的原因开始:
由于硅设计(为此他们使用了一些硬件技巧)无法扩展 4.
所以 最后一个 Amdahl's Law 解释并促进了加速 +1
处理器 的升级计数是 4并且任何下一个 +1 都不会以在 { 2, 3, 4 }-case 中观察到的相同方式提升性能:
这个lstopo
CPU-topology map helps to start decode WHY(这里是4核的,但是逻辑是一样的您的 8 核芯片 - 运行 lstopo
在您的设备上查看更多体内细节):
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Machine (31876MB) │
│ │
│ ┌────────────────────────────────────────────────────────────┐ ┌───────────────────────────┐ │
│ │ Package P#0 │ ├┤╶─┬─────┼┤╶───────┤ PCI 10ae:1F44 │ │
│ │ │ │ │ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │ │ ┌────────────┐ ┌───────┐ │ │
│ │ │ L3 (8192KB) │ │ │ │ │ renderD128 │ │ card0 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │ │ └────────────┘ └───────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ ┌────────────┐ │ │
│ │ │ L2 (2048KB) │ │ L2 (2048KB) │ │ │ │ │ controlD64 │ │ │
│ │ └──────────────────────────┘ └──────────────────────────┘ │ │ │ └────────────┘ │ │
│ │ │ │ └───────────────────────────┘ │
│ │ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │
│ │ │ L1i (64KB) │ │ L1i (64KB) │ │ │ ┌───────────────┐ │
│ │ └──────────────────────────┘ └──────────────────────────┘ │ ├─────┼┤╶───────┤ PCI 10bc:8268 │ │
│ │ │ │ │ │ │
│ │ ┌────────────┐┌────────────┐ ┌────────────┐┌────────────┐ │ │ │ ┌────────┐ │ │
│ │ │ L1d (16KB) ││ L1d (16KB) │ │ L1d (16KB) ││ L1d (16KB) │ │ │ │ │ enp2s0 │ │ │
│ │ └────────────┘└────────────┘ └────────────┘└────────────┘ │ │ │ └────────┘ │ │
│ │ │ │ └───────────────┘ │
│ │ ┌────────────┐┌────────────┐ ┌────────────┐┌────────────┐ │ │ │
│ │ │ Core P#0 ││ Core P#1 │ │ Core P#2 ││ Core P#3 │ │ │ ┌──────────────────┐ │
│ │ │ ││ │ │ ││ │ │ ├─────┤ PCI 1002:4790 │ │
│ │ │ ┌────────┐ ││ ┌────────┐ │ │ ┌────────┐ ││ ┌────────┐ │ │ │ │ │ │
│ │ │ │ PU P#0 │ ││ │ PU P#1 │ │ │ │ PU P#2 │ ││ │ PU P#3 │ │ │ │ │ ┌─────┐ ┌─────┐ │ │
│ │ │ └────────┘ ││ └────────┘ │ │ └────────┘ ││ └────────┘ │ │ │ │ │ sr0 │ │ sda │ │ │
│ │ └────────────┘└────────────┘ └────────────┘└────────────┘ │ │ │ └─────┘ └─────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │ └──────────────────┘ │
│ │ │
│ │ ┌───────────────┐ │
│ └─────┤ PCI 1002:479c │ │
│ └───────────────┘ │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
仔细观察,就像对 hwloc
-tool: lstopo-no-graphics -.ascii
的调用一样,显示 相互处理独立ends - 这里处于 shared L1
-instruction-cache 的级别(L3
是共享的,但是在层次结构的顶部,其大小仅对大型问题解决者造成困扰,而不是我们的情况)
接下来是一个更糟糕的可观察到的原因为什么在 8 个进程上更糟:
Q : "Why does running 8 in parallel not twice as fast as running 4 in parallel i.e. why is it not ~3.5s
?"
由于热管理。
加载到 CPU 核上的工作越多,通过硅迷宫驱动 ~3.5+ GHz
上的电子产生的热量就越多。热限制是那些阻止 CPU 计算能力进一步提高性能的限制,仅仅因为物理定律,正如我们所知,不允许超出某些 material 定义的限制。
接下来会发生什么?
CPU-设计没有规避物理学(那是不可能的),而是我们,用户 - 通过向我们承诺具有 ~3.5+ GHz
[=88= 的 CPU 芯片](但实际上,CPU 只能在很短的时间内使用此时钟速率 - 直到散发的热量不会使硅接近热极限 - 然后,CPU将决定 降低自己的时钟频率 作为过热防御措施(这会降低性能,不是吗?)或 一些 CPU-微架构可能会跳(移动处理流程)到另一个免费的,因此更酷的CPU-核心(它承诺更高的时钟速率那里(至少在一些小的时间)但也降低了性能,因为跃点不会在零时间发生并且不会在零成本下发生(缓存 -损失,重新获取等)
此图片显示核心跳跃情况的快照 - 核心 0-19
变得太热并且在热节流上限之下,而核心 20-39
可以(至少现在)运行 全速:
结果?
两个热约束(潜水 CPU 进入液氮池是为“流行”的杂志节目演示的,但对于任何可持续计算来说都不是一个合理的选择,因为从运行中产生的机械压力从深度冻结状态进入 6+ GHz
时钟速率蒸汽形成过热器会使 CPU 的主体破裂并导致 CPU-由于裂纹和机械疲劳而死亡,但只有少数工作量发作 - 所以这是一个禁区,因为 负 ROI 对于任何严肃的项目)。
基于体内预测试的良好冷却和适当规模的工人池是这里唯一确定的赌注。
其他架构:
正在尝试通过 python multiprocessing 找到 运行 的正确并行进程数。
下面的脚本是 运行 在 8 核 32 GB (Ubuntu 18.04) 机器上的。 (只有系统进程和基本用户进程 运行ning 测试了以下内容。)
使用以下内容测试了 multiprocessing.Pool
和 apply_async
:
from multiprocessing import current_process, Pool, cpu_count
from datetime import datetime
import time
num_processes = 1 # vary this
print(f"Starting at {datetime.now()}")
start = time.perf_counter()
print(f"# CPUs = {cpu_count()}") # 8
num_procs = 5 * cpu_count() # 40
def cpu_heavy_fn():
s = time.perf_counter()
print(f"{datetime.now()}: {current_process().name}")
x = 1
for i in range(1, int(1e7)):
x = x * i
x = x / i
t_taken = round(time.perf_counter() - s, 2)
return t_taken, current_process().name
pool = Pool(processes=num_processes)
multiple_results = [pool.apply_async(cpu_heavy_fn, ()) for i in range(num_procs)]
results = [res.get() for res in multiple_results]
for r in results:
print(r[0], r[1])
print(f"Done at {datetime.now()}")
print(f"Time taken = {time.perf_counter() - start}s")
结果如下:
num_processes total_time_taken
1 28.25
2 14.28
3 10.2
4 7.35
5 7.89
6 8.03
7 8.41
8 8.72
9 8.75
16 8.7
40 9.53
以下对我有意义:
- 运行一次一个进程每个进程大约需要0.7秒,所以运行ning 40应该大约需要28s,这与我们上面观察到的一致。
- 运行 一次 2 个进程应该将时间减半,这是在上面观察到的(~14s)。
- 运行 一次 4 个进程应该进一步减半时间,这在上面观察到(~7s)。
- 将并行度增加到超过内核数 (8) 应该会降低性能(由于 CPU 争用),这是观察到的(某种程度上)。
没有意义的是:
- 为什么 运行ning 8 并联的速度不是 运行ning 4 并联的两倍,即为什么不是 ~3.5s?
- 为什么运行一次并发 5 到 8 个比 运行一次并发 4 个差?有 8 个核心,但为什么整体 运行 时间更差? (当 运行ning 8 并行时,
htop
显示所有 CPU 的利用率接近 100%。当 运行ning 4 并行时,只有 4 个处于 100%这是有道理的。)
最可能的原因是您 运行 CPU 上的程序在 Intel 设备上使用 simultaneous multithreading (SMT), better known as hyper-threading。在 wiki 之后引用,对于物理上存在的每个处理器核心,操作系统寻址两个虚拟(逻辑)核心并在可能的情况下在它们之间分担工作负载。这就是这里发生的事情。
你的OS说是8核,其实是4核带SMT。任务显然是 CPU-bound,因此任何超出 physical 核心数量的增加都不会带来任何好处,只会带来多处理的开销成本。这就是为什么您会看到性能几乎呈线性增长,直到达到(物理!)最大值。核心数量 (4),然后在需要为这项非常 CPU 密集型任务共享核心时减少。
Q : "Why is running 5 to 8 in parallel at a time worse than running 4 at a time?"
好吧,有几个原因,我们将从一个静态的、最容易观察到的原因开始:
由于硅设计(为此他们使用了一些硬件技巧)无法扩展 4.
所以 最后一个 Amdahl's Law 解释并促进了加速 +1
处理器 的升级计数是 4并且任何下一个 +1 都不会以在 { 2, 3, 4 }-case 中观察到的相同方式提升性能:
这个lstopo
CPU-topology map helps to start decode WHY(这里是4核的,但是逻辑是一样的您的 8 核芯片 - 运行 lstopo
在您的设备上查看更多体内细节):
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Machine (31876MB) │
│ │
│ ┌────────────────────────────────────────────────────────────┐ ┌───────────────────────────┐ │
│ │ Package P#0 │ ├┤╶─┬─────┼┤╶───────┤ PCI 10ae:1F44 │ │
│ │ │ │ │ │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │ │ ┌────────────┐ ┌───────┐ │ │
│ │ │ L3 (8192KB) │ │ │ │ │ renderD128 │ │ card0 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │ │ └────────────┘ └───────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ ┌────────────┐ │ │
│ │ │ L2 (2048KB) │ │ L2 (2048KB) │ │ │ │ │ controlD64 │ │ │
│ │ └──────────────────────────┘ └──────────────────────────┘ │ │ │ └────────────┘ │ │
│ │ │ │ └───────────────────────────┘ │
│ │ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │
│ │ │ L1i (64KB) │ │ L1i (64KB) │ │ │ ┌───────────────┐ │
│ │ └──────────────────────────┘ └──────────────────────────┘ │ ├─────┼┤╶───────┤ PCI 10bc:8268 │ │
│ │ │ │ │ │ │
│ │ ┌────────────┐┌────────────┐ ┌────────────┐┌────────────┐ │ │ │ ┌────────┐ │ │
│ │ │ L1d (16KB) ││ L1d (16KB) │ │ L1d (16KB) ││ L1d (16KB) │ │ │ │ │ enp2s0 │ │ │
│ │ └────────────┘└────────────┘ └────────────┘└────────────┘ │ │ │ └────────┘ │ │
│ │ │ │ └───────────────┘ │
│ │ ┌────────────┐┌────────────┐ ┌────────────┐┌────────────┐ │ │ │
│ │ │ Core P#0 ││ Core P#1 │ │ Core P#2 ││ Core P#3 │ │ │ ┌──────────────────┐ │
│ │ │ ││ │ │ ││ │ │ ├─────┤ PCI 1002:4790 │ │
│ │ │ ┌────────┐ ││ ┌────────┐ │ │ ┌────────┐ ││ ┌────────┐ │ │ │ │ │ │
│ │ │ │ PU P#0 │ ││ │ PU P#1 │ │ │ │ PU P#2 │ ││ │ PU P#3 │ │ │ │ │ ┌─────┐ ┌─────┐ │ │
│ │ │ └────────┘ ││ └────────┘ │ │ └────────┘ ││ └────────┘ │ │ │ │ │ sr0 │ │ sda │ │ │
│ │ └────────────┘└────────────┘ └────────────┘└────────────┘ │ │ │ └─────┘ └─────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │ └──────────────────┘ │
│ │ │
│ │ ┌───────────────┐ │
│ └─────┤ PCI 1002:479c │ │
│ └───────────────┘ │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
仔细观察,就像对 hwloc
-tool: lstopo-no-graphics -.ascii
的调用一样,显示 相互处理独立ends - 这里处于 shared L1
-instruction-cache 的级别(L3
是共享的,但是在层次结构的顶部,其大小仅对大型问题解决者造成困扰,而不是我们的情况)
接下来是一个更糟糕的可观察到的原因为什么在 8 个进程上更糟:
Q : "Why does running 8 in parallel not twice as fast as running 4 in parallel i.e. why is it not
~3.5s
?"
由于热管理。
加载到 CPU 核上的工作越多,通过硅迷宫驱动 ~3.5+ GHz
上的电子产生的热量就越多。热限制是那些阻止 CPU 计算能力进一步提高性能的限制,仅仅因为物理定律,正如我们所知,不允许超出某些 material 定义的限制。
接下来会发生什么?
CPU-设计没有规避物理学(那是不可能的),而是我们,用户 - 通过向我们承诺具有 ~3.5+ GHz
[=88= 的 CPU 芯片](但实际上,CPU 只能在很短的时间内使用此时钟速率 - 直到散发的热量不会使硅接近热极限 - 然后,CPU将决定 降低自己的时钟频率 作为过热防御措施(这会降低性能,不是吗?)或 一些 CPU-微架构可能会跳(移动处理流程)到另一个免费的,因此更酷的CPU-核心(它承诺更高的时钟速率那里(至少在一些小的时间)但也降低了性能,因为跃点不会在零时间发生并且不会在零成本下发生(缓存 -损失,重新获取等)
此图片显示核心跳跃情况的快照 - 核心 0-19
变得太热并且在热节流上限之下,而核心 20-39
可以(至少现在)运行 全速:
结果?
两个热约束(潜水 CPU 进入液氮池是为“流行”的杂志节目演示的,但对于任何可持续计算来说都不是一个合理的选择,因为从运行中产生的机械压力从深度冻结状态进入 6+ GHz
时钟速率蒸汽形成过热器会使 CPU 的主体破裂并导致 CPU-由于裂纹和机械疲劳而死亡,但只有少数工作量发作 - 所以这是一个禁区,因为 负 ROI 对于任何严肃的项目)。
基于体内预测试的良好冷却和适当规模的工人池是这里唯一确定的赌注。
其他架构: