Почему parLapplyLB фактически не балансирует нагрузку?

Я тестирую функцию 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)

Если бы это была действительно балансировка нагрузки, первое задание (задание 1), которое будет спать в течение 3 секунд, будет на первом node, а остальные три задания (задания 2: 4) будут спать в общей сложности 1,5 секунды с другой node. В общем, системное время должно быть всего 3 секунды.

Вместо этого я думаю, что задания 1 и 2 даны node 1, а задания 3 и 4 даются node 2. В результате общее время составляет 3 + 0,5 = 3,5 секунды. Если мы запускаем тот же код выше с parLapply() вместо parLapplyLB(), мы получаем то же самое системное время около 3,5 секунд.

Что я не понимаю и не делаю неправильно?

Ответ 1

Для задачи, подобной вашей (и, если на то пошло, для любой задачи, для которой мне когда-либо понадобился parallel) 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() в своем заданном подсписке. Таким образом, ваш первый node запускает задания на первом и втором входах, а второй node выполняет задания с использованием третьего и четвертого входов.

Вместо этого используйте более универсальный 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 

Ответ 2

parLapplyLB не балансирует нагрузку, поскольку имеет семантическую ошибку. Мы обнаружили ошибку и предоставили исправление, см. здесь. Теперь его до R-разработчиков включить исправление.