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



当我阅读多核并行计算的Julia文档时,我注意到有并行映射pmap和for循环@distributed for

从文档中看,"Julia 的pmap是为每个函数调用执行大量工作的情况而设计的。相比之下,@distributed for可以处理每次迭代都很小的情况"。

pmap@distributed for有什么区别?为什么@distributed for对于大量工作来说很慢?

谢谢

问题是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,因为它会进行负载平衡。

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

但是,@distributedfor 循环在开始时在工作线程之间对给定范围进行一次分区,不知道该范围的每个分区将花费多少时间。例如,考虑一个矩阵集合,其中集合的前一百个元素是 2×2 矩阵,接下来的一百个元素是 1000 x 1000 个矩阵,我们希望使用@distributedfor 循环和 2 个工作进程获取每个矩阵的逆矩阵。

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

第一个工作线程将获得所有 2×2 矩阵,第二个工作线程将获得 1000 x 1000 矩阵。第一个工作人员将很快完成所有转换并闲置,而另一个工作人员将继续长时间工作。虽然您使用 2 个工作线程,但整个工作的主要部分将在第二个工作线程上有效地串行执行,并且使用多个工作线程几乎没有任何好处。此问题在并行计算的上下文中称为负载平衡。例如,当一个处理器很慢而另一个处理器很快时,即使要完成的工作是同质的,也可能会出现问题。

但是,对于非常小的工作转换,使用具有小批量大小的pmap会产生通信开销,这可能很大,因为在每个批处理之后,处理器需要从调用进程中获取下一个批处理,而对于@distributedfor-loop,每个工作进程在开始时都会知道它负责范围的哪一部分。

pmap@distributedfor 循环之间的选择取决于您要实现的目标。如果您要像map一样转换集合,并且每次转换都需要大量工作并且工作量各不相同,那么选择pmap可能会更好。如果每个转换都非常小,那么选择@distributedfor 循环可能会更好。

请注意,如果在转换后需要缩减操作,@distributedfor 循环已经提供了一个,则大部分缩减将在本地应用,而最终的缩减将在调用过程中进行。但是,使用pmap,您将需要自己处理减少。

如果您确实需要,您还可以使用非常复杂的负载平衡和减少方案来实现自己的pmap功能。

https://docs.julialang.org/en/v1/manual/parallel-computing/

最新更新