为什么 parLapplyLB 实际上并没有平衡负载?
Why does parLapplyLB not actually balance load?
我正在测试 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 秒的相同系统时间。
我哪里不理解或做错了什么?
注意: 自 R-3.5.0 起,OP 指出并在下面解释的 behavior/bug 已得到修复。正如当时 R 的 NEWS
文件中所述:
* 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
parLapplyLB
没有平衡负载,因为它有语义错误。我们发现了该错误并提供了修复,请参阅 here。现在,由 R 开发人员来包含修复程序。
我正在测试 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 秒的相同系统时间。
我哪里不理解或做错了什么?
注意: 自 R-3.5.0 起,OP 指出并在下面解释的 behavior/bug 已得到修复。正如当时 R 的 NEWS
文件中所述:
* 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
parLapplyLB
没有平衡负载,因为它有语义错误。我们发现了该错误并提供了修复,请参阅 here。现在,由 R 开发人员来包含修复程序。