15

我正在测试该parLapplyLB()功能以了解它如何平衡负载。但我没有看到任何平衡发生。例如,

cl <- parallel::makeCluster(2)

system.time(
  parallel::parLapplyLB(cl, 1:4, function(y) {
    if (y == 1) {
      Sys.sleep(3)
    } else {
      Sys.sleep(0.5)
    }}))
##   user  system elapsed 
##  0.004   0.009   3.511 

parallel::stopCluster(cl)

如果它真正平衡负载,则休眠 3 秒的第一个作业(作业 1)将在第一个节点上,而其他三个作业(作业 2:4)将在另一个节点上总共休眠 1.5 秒。总的来说,系统时间应该只有 3 秒。

相反,我认为作业 1 和 2 分配给节点 1,作业 3 和 4 分配给节点 2。这导致总时间为 3 + 0.5 = 3.5 秒。如果我们使用parLapply()而不是运行上面的相同代码parLapplyLB(),我们会得到大约 3.5 秒的相同系统时间。

我不理解或做错了什么?

4

2 回答 2

14

注意:自 R-3.5.0 起,OP 指出并在下面解释的行为/错误已得到修复。正如NEWS当时 R 的文件中所述:

* parLapplyLB and parSapplyLB have been fixed to do load balancing
  (dynamic scheduling).  This also means that results of
  computations depending on random number generators will now
  really be non-reproducible, as documented.

原始答案(现在仅适用于 R 版本 < 3.5.0 )

对于像您这样的任务(并且,就此而言,对于我曾经需要并行的任何任务)parLapplyLB并不是真正适合这项工作的工具。要了解为什么不这样做,请查看它的实现方式:

parLapplyLB
# function (cl = NULL, X, fun, ...) 
# {
#     cl <- defaultCluster(cl)
#     do.call(c, clusterApplyLB(cl, x = splitList(X, length(cl)), 
#         fun = lapply, fun, ...), quote = TRUE)
# }
# <bytecode: 0x000000000f20a7e8>
# <environment: namespace:parallel>

## Have a look at what `splitList()` does:
parallel:::splitList(1:4, 2)
# [[1]]
# [1] 1 2
# 
# [[2]]
# [1] 3 4

问题在于它首先将其作业列表拆分为大小相等的子列表,然后在节点之间分配这些子列表,每个节点都lapply()在其给定的子列表上运行。所以在这里,您的第一个节点在第一个和第二个输入上运行作业,而第二个节点使用第三个和第四个输入运行作业。

相反,请使用更通用的clusterApplyLB(),它的工作原理与您希望的一样:

system.time(
  parallel::clusterApplyLB(cl, 1:4, function(y) {
    if (y == 1) {
      Sys.sleep(3)
    } else {
      Sys.sleep(0.5)
    }}))
# user  system elapsed 
# 0.00    0.00    3.09 
于 2016-07-06T18:45:39.290 回答
4

parLapplyLB没有平衡负载,因为它有一个语义错误。我们发现了错误并提供了修复,请参见此处。现在,由 R 开发人员负责修复。

于 2018-02-14T06:57:23.260 回答