如何获得并发函数(pmap)以使用 Elixir 中的所有内核?

How to get concurrent function (pmap) to use all cores in Elixir?

我是 Elixir 的新手,我开始通读 Dave Thomas 的优秀Elixir 编程。我很好奇我可以将 "pmap" 函数的并发性提高到什么程度,所以我迭代地将项目数从 1,000 增加到 10,000,000。出于好奇,我观察了 htop 的输出,通常以 CPU 用法达到峰值,如下所示:

在展示书中的示例后,Dave 说:

And, yes, I just kicked off 1,000 background processes, and I used all the cores and processors on my machine.

我的问题是,为什么我的机器上只有核心 1、3、5 和 7 亮着?我的猜测是,这与我的 iex 进程只是一个 OS 级别的进程有关,而 OSX 正在管理该进程的范围。这就是这里发生的事情吗?有什么方法可以确保所有内核都用于性能密集型任务吗?

@Thiago Silveira 关于 iex 输出的第一行的精彩评论。 [smp:8:8] 部分表示 Erlang 使用了多少操作系统级进程。如果你想禁用它,你可以用标志 --smp 来控制它:

iex --erl '-smp disable'

这将确保您只有一个系统进程。您可以通过启用对称多处理来实现类似的结果,但直接设置 NumberOfShcedulers:NumberOfSchedulersOnline.

iex --erl '+S 1:1'

每个操作系统进程都需要有自己的 Erlang 进程调度程序,因此您可以轻松查看当前有多少个:

:erlang.system_info(:schedulers_online)

回答您关于性能的问题。如果您的处理器没有满负荷工作 (100%) 并且没有一个处理器什么也不做 (0%),那么很可能使负载更均匀地分布不会加快速度。为什么?

CPU 使用情况是通过在多个时间点探测处理器状态来衡量的。此状态为 "working" 或 "idle"。 82% CPU 使用率意味着您可以在此 CPU 上执行更多任务,而不会减慢其他任务。

Erlang 调度器尝试变得聪明,不会在内核之间迁移 Erlang 进程,除非他们必须这样做,因为它需要复制。例如,当其中一个调度程序空闲时,就会发生迁移。然后它可以从其他调度程序 运行 队列中借用一个进程。

接下来可能导致奇数核和偶数核之间存在如此大差异的是超线程。在我的双核处理器上 htop 显示 4 个逻辑核心。在您的情况下,由于 HT,您可能有 4 个物理内核和 8 个逻辑内核。可能是您 100% 地利用了物理内核。

另一件事:pmap 需要在单独的进程中计算结果,但最后它会将其发送给调用者,这可能是一个瓶颈。您发送的消息越多,您可以实现的 CPU 利用率就越低。您可以尝试给进程一个真正 CPU 密集的任务,比如计算 Ackerman function. You can even calculate how much of your job is the sequential part and how much is parallel using Amdahl's law 和测量不同内核数的执行时间。

总结一下:截图中的CPU利用率看起来真的很棒!您无需为性能更密集的任务更改任何内容。

并发不是并行

为了从 Elixir/BEAM 编码中获得良好的并行性能,您需要了解 BEAM 调度程序的工作原理。

这是一个非常简单的模型,但是 BEAM 调度程序在将进程换成下一个进程之前为每个进程提供 2000 个减少量。减少可以被认为是函数调用。默认情况下,进程在生成它的 core/scheduler 上运行。如果未完成的进程队列在给定的调度程序上建立起来,进程只会在调度程序之间移动。默认情况下,BEAM 在每个可用核心上运行一个调度线程。

这意味着,为了充分利用处理器,您需要将任务分解成足够大的工作块,这些工作块将超过标准 "reduction" 工作块。一般而言,pmap 样式的并行性仅在您将许多项目分块到单个任务中时才提供显着的加速。

另一件需要注意的事情是,BEAM 的某些部分在等待工作时使用 spin/wait 循环,当您使用 像 htop 这样的工具来检查 CPU 用法。通过使用 :observer,您将更好地了解程序的性能。