Почему 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 секунд, было бы на первом узле, а остальные три задания (задания 2:4) спали бы в течение 1,5 секунд на другом узле. В общей сложности системное время должно составлять всего 3 секунды.

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

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

2 ответа

Решение

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

Другие вопросы по тегам