I'm testing out the parLapplyLB()
function to understand what it does to balance a load. But I'm not seeing any balancing happening. For example,
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)
If it was truly balancing the load, the first job (job 1) that sleeps for 3 seconds would be on the first node and the other three jobs (jobs 2:4) would sleep for a total of 1.5 seconds on the other node. In total, the system time should only be 3 seconds.
Instead, I think that jobs 1 and 2 are given to node 1 and jobs 3 and 4 are given to node 2. This results in the total time being 3 + 0.5 = 3.5 seconds. If we run the same code above with parLapply()
instead of parLapplyLB()
, we get the same system time of about 3.5 seconds.
What am I not understanding or doing wrong?
NOTE: Since R-3.5.0, the behavior/bug noted by the OP and explained below has been fixed. As noted in R's NEWS
file at the time:
* 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.
ORIGINAL ANSWER (Now only relevant for R versions < 3.5.0 )
For a task like yours (and, for that matter, for any task for which I've ever needed parallel) parLapplyLB
isn't really the right tool for the job. To see why not, have a look at the way that it's implemented:
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
The problem is that it first splits its list of jobs up into equal-sized sublists that it then distributes among the nodes, each of which runs lapply()
on its given sublist. So here, your first node runs jobs on the first and second inputs, while the second node runs jobs using the third and fourth inputs.
Instead, use the more versatile clusterApplyLB()
, which works just as you'd hope:
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