Почему 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   12  

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