Почему фьючерсы второго уровня исполняются последовательно?

Я пытаюсь повторить примеры в топологии виньетки будущего пакета. Цитировать:

Фьючерсы могут быть вложены в R так, что одно будущее создает другой набор фьючерсов и так далее. Это может происходить, например, в рамках вложенных циклов for [...]

Есть часть, где автор использует plan(list(multicore, multicore)) (дальнейшие аргументы и tweak опущено) обрабатывать два фьючерса синхронно, который в терминах каждого обрабатывает четыре фьючерса синхронно. Это должно равняться восьми фьючерсам, обрабатываемым синхронно.

Однако, когда я пытаюсь воспроизвести это с помощью приведенного ниже кода, я вижу, что второй уровень фьючерсов обрабатывается последовательно. Что я делаю неправильно?

MCVE

library(future)
library(ggplot2)
plan(list(multiprocess, multiprocess))


# Run for a random amount of time and return start and stop time
startStop <- function(){
  start <- Sys.time()
  x <- runif(1, 1, 3)
  Sys.sleep(x)
  stop <- Sys.time()
  return(data.frame(start = start, stop = stop))
}

nGrp <- 3
nCV <- 4

l <- rep(list(NULL), nGrp)


for(i in seq_along(l)){
  l[[i]] <- future({
    m <- rep(list(NULL), nCV)
    for(j in seq_along(m)){
      m[[j]] <- future(startStop())
    }
    m <- lapply(m, value)
    m <- do.call(rbind, m)
    m
  })
}
l <- lapply(l, value)
d <- do.call(rbind, l)
d$iGrp <- rep(seq_len(nGrp), each = nCV)
d$iCV <- rep(seq_len(nCV), times = nGrp)

d$x <- paste(d$iGrp, d$iCV, sep = "_")
d$iGrp <- as.character(d$iGrp)
ggplot(d, aes(x = x, ymin = start, ymax = stop, color = iGrp)) + geom_linerange() + coord_flip()

временная эволюция исполнения фьючерсов

ожидание

ожидание

Информация о сессии

R version 3.4.3 (2017-11-30)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: CentOS Linux 7 (Core)

Matrix products: default
BLAS: /opt/Bio/R/3.4.3/lib64/R/lib/libRblas.so
LAPACK: /opt/Bio/R/3.4.3/lib64/R/lib/libRlapack.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_2.2.1 future_1.8.1 

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.17     devtools_1.13.4  munsell_0.4.3    colorspace_1.3-2
 [5] R6_2.2.2         rlang_0.1.6      httr_1.3.1       plyr_1.8.4      
 [9] globals_0.11.0   tools_3.4.3      parallel_3.4.3   grid_3.4.3      
[13] gtable_0.2.0     git2r_0.21.0     withr_2.1.1      yaml_2.1.16     
[17] lazyeval_0.2.1   digest_0.6.15    tibble_1.4.2     codetools_0.2-15
[21] curl_3.1         memoise_1.1.0    compiler_3.4.3   pillar_1.1.0    
[25] scales_0.5.0     listenv_0.7.0 

2 ответа

Решение

Автор будущего здесь: Это потому, что есть встроенная защита от вложенного параллелизма. Без него вы бы перегружали компьютер слишком большим количеством параллельных процессов, что не только перегревало бы его, но и замедляло бы общую производительность.

Я обновил виньетку "Будущие топологии" для следующего выпуска следующим разделом:

Встроенная защита от рекурсивного параллелизма

Выше мы параллельно обрабатывали либо внешний, либо внутренний набор будущего. Что если мы хотим обрабатывать оба слоя параллельно? Заманчиво использовать:

plan(list(multiprocess, multiprocess))

Хотя это не дает ошибки, мы обнаружим, что внутренний слой фьючерсов будет обрабатываться последовательно, как если бы мы использовали plan(list(multiprocess, sequential)), Такое поведение обусловлено встроенной защитой от вложенного параллелизма. Если бы оба слоя работали параллельно, каждый из которых использовал бы 8 ядер, имеющихся на машине, мы бы запустили 8 * 8 = 64 параллельных процесса - это наверняка перегрузило бы наш компьютер. Что происходит внутри, так это для внешнего слоя, availableCores() равен восьми (8), тогда как для внутреннего слоя он равен единице (1).

Теперь мы можем представить, что мы обрабатываем внешний слой, скажем, двумя параллельными фьючерсами, а затем внутренний слой четырьмя параллельными фьючерсами. В этом случае мы будем работать максимум на восьми ядрах (= 2 * 4). Это может быть достигнуто путем принудительного фиксирования числа рабочих на каждом слое (не рекомендуется):

plan(list(tweak(multiprocess, workers = 2), tweak(multiprocess, workers = 4)))

Если вы хотите добиться параллельной обработки, как и ожидалось, то в будущем вам подойдет future.callr. Просто используйте: library(future.callr) plan(list(callr, callr))

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