并行映射和并行for循环的区别

10
当我阅读 Julia 的多核并行计算文档时,我注意到有并行映射 pmap 和 for 循环 @distributed for 两种方法。
从文档中可以了解到:"Julia 的 pmap 是针对每个函数调用都需要大量工作的情况而设计的。相反,@distributed for 可以处理每次迭代操作非常小的情况"。
那么,是什么造成了 pmap@distributed for 的区别呢?为什么在大量工作的情况下 @distributed for 会变得很慢?
谢谢。
2个回答

8
问题在于

pmap会进行负载平衡,而@distributed for会将作业分成相等的块。您可以通过运行以下两个代码示例来确认此问题:

julia> @time res = pmap(x -> (sleep(x/10); println(x)), [10;ones(Int, 19)]);
      From worker 2:    1
      From worker 3:    1
      From worker 4:    1
      From worker 2:    1
      From worker 3:    1
      From worker 4:    1
      From worker 3:    1
      From worker 2:    1
      From worker 4:    1
      From worker 4:    1
      From worker 2:    1
      From worker 3:    1
      From worker 2:    1
      From worker 3:    1
      From worker 4:    1
      From worker 4:    1
      From worker 3:    1
      From worker 2:    1
      From worker 4:    1
      From worker 5:    10
  1.106504 seconds (173.34 k allocations: 8.711 MiB, 0.66% gc time)

julia> @time @sync @distributed for x in [10;ones(Int, 19)]
       sleep(x/10); println(x)
       end
      From worker 4:    1
      From worker 3:    1
      From worker 5:    1
      From worker 4:    1
      From worker 5:    1
      From worker 3:    1
      From worker 5:    1
      From worker 3:    1
      From worker 4:    1
      From worker 3:    1
      From worker 4:    1
      From worker 5:    1
      From worker 4:    1
      From worker 5:    1
      From worker 3:    1
      From worker 2:    10
      From worker 2:    1
      From worker 2:    1
      From worker 2:    1
      From worker 2:    1
  1.543574 seconds (184.19 k allocations: 9.013 MiB)
Task (done) @0x0000000005c5c8b0

您可以看到,当大型作业(值为10)时,pmap会将所有小型作业分配给不同于获得大型作业的工作程序的工作程序执行(在我的示例中,工作程序5仅执行作业10,而工作程序24执行所有其他作业)。另一方面,@distributed for将相同数量的作业分配给每个工作程序。因此,获得作业10的工作程序(在第二个示例中是工作程序2)仍然必须执行四个短作业(因为每个工作程序平均需要处理5个作业-我的示例共有20个作业和4个工作程序)。

现在@distributed for的优点在于,如果作业成本低廉,则将作业平均分配给工作程序避免了需要进行动态调度(这也不是免费的)。

总之,正如文档所述,如果作业昂贵(特别是其运行时间可能变化很大),最好使用pmap,因为它具有负载平衡的功能。


6

pmap有一个batch_size参数,默认为1。这意味着集合的每个元素都将逐一发送到可用的工作进程或任务中,由您提供的函数进行转换。如果每个函数调用执行大量的工作,并且每个调用所需的时间不同,使用pmap的优点是不会让工人空闲,而其他工人在做工作,因为当一个工人完成一个转换时,它将要求处理下一个元素。因此,pmap有效地在工作进程/任务之间“平衡负载”。

@distributed循环,然而,最初会将给定范围的分区分配给工作进程,但并不知道每个分区需要多长时间。例如,考虑一个矩阵集合,其中前100个元素是2×2矩阵,接下来的100个元素是1000×1000矩阵,我们想使用@distributed循环和2个工作进程来求每个矩阵的逆。

@sync @distributed for i = 1:200
    B[i] = inv(A[i])
end

第一个工作进程将获取所有的2x2矩阵,第二个进程将获取1000x1000的矩阵。第一个进程会很快地完成所有的转换并空闲下来,而另一个进程将继续工作很长时间。虽然您使用了2个工作进程,但整个工作的主要部分将在第二个进程上有效地串行执行,因此使用多个进程几乎不会带来任何好处。在并行计算环境中,这个问题被称为负载平衡问题。即使要完成的工作是同构的,例如当一个处理器速度较慢而另一个处理器速度较快时,此问题也可能出现。
然而,对于非常小的工作转换,使用小批量大小的pmap会创建一些显著的通信开销,因为在每个批次之后,处理器需要从调用进程获取下一个批次,而使用@distributed for-loops,则每个工作进程将在开始时知道其所负责的范围。
选择pmap@distributed for-loop取决于您的目标。如果您要像map那样转换一个集合,并且每个转换需要大量工作且该量不断变化,则您可能更喜欢选择pmap。如果每个转换非常微小,则您可能更喜欢选择@distributed for-loop。
请注意,如果在转换后需要进行约减操作,则@distributed for-loop已经提供了一个,大多数约减将在本地应用,而最终的约减将在调用进程上执行。但是,使用pmap,您需要自己处理约减。
如果确实需要一个非常复杂的负载平衡和约减方案,您也可以实现自己的pmap函数。
参考链接:https://docs.julialang.org/en/v1/manual/parallel-computing/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接