ClusterMap в R на разветвленном кластере работает намного медленнее, чем mapply. Что еще я могу попробовать?

Я новичок в параллельном программировании и почерпнул информацию о R из различных (иногда противоречивых) источников. Я унаследовал некоторый код и попытался распараллелить его, я думаю, что распараллеливание данных уместно.

Задача требует некоторых вычислений для ряда особей (этап 1), затем вычисленные величины используются для расчета некоторых характеристик совокупности (этап 2). Мы делаем это для цикла с заданным числом итераций. У меня есть подозрение (собирая воедино биты, которые я прочитал), что вычисления из фазы 1 вызывают изменение объекта, хранящего результаты, из-за чего вычисления страдают от некоторого времени ожидания, вызванного (нежелательным) копированием объектов. В результате выполнение программы с использованием clusterMap занимает гораздо больше системного времени, чем непараллельная версия кода с использованием mapply.

Вычисления требуют многих уровней вызовов функций. Я попытался дать представление о структуре кода на небольшом рабочем примере ниже.

 require(parallel)

## define the two phases of the computation 
phase1.top.level<- function(x,y){second.level(x,y)}

## Functions call other functions.  
phase1.second.level<- function(x,y){   third.level(x,y) }

phase1.third.level<- function(x,y){ result<- x^2-2*y*x; result }

 ## define the function that performs the second phase of the computation     
second.phase<-function(input,x,y) {input+x+y}

## Assemble these functions into an algorithm
multi.phase.comp<- function(input,x,y){

    input<-phase1.top.level(x,y)
        output<-second.phase(input,x,y)
}


### Call the function with inputs
 x<-seq(1:100)
y<-seq(1:100)
input<-rep(1,100)

## Time the function executed for the vector inputs using clusterMap from the parallel package
system.time({ 
no_cores<-5 
    c1<- makeCluster(no_cores,type="FORK")
out<- clusterMap(cl=c1, multi.phase.comp, input, x, y)
stopCluster(c1) 
})
### One execution of this (on an old laptop) gave
###  user  system elapsed 
###  0.055   0.163   0.973 

 ## Regrettably, the effort to parallelise wasn't justified as mapply is    faster: 
system.time({ 
out2<-  mapply( multi.phase.comp, input, x, y)
})
#### This shows much less system and elapsed time 
#### user  system elapsed 
#### 0.009   0.002   0.030 

Любые предложения о том, как я могу ускорить свой код? Будет ли мне лучше использовать кластеры сокетов для этого типа приложений?


person Jason Whyte    schedule 04.12.2016    source источник
comment
Проблемы с почти нулевым временем вычислений не стоит выполнять параллельно, поскольку время вычислений намного меньше, чем накладные расходы на отправку задач и получение результатов от рабочих процессов. Если задачи в вашей реальной задаче выполняются даже за 0,1 секунды, параллельная версия должна превзойти mapply. Но нет особого смысла пытаться улучшить производительность этой игрушечной задачи, так как вы никогда не сможете побить 0,03 секунды.   -  person Steve Weston    schedule 05.12.2016
comment
@SteveWeston, реальный код гораздо сложнее, он слишком длинный и запутанный, чтобы служить иллюстрацией. Вот реализация фактического времени выполнения: открытие кластера внутри одного из нижних уровней функции дает пользовательской системе истекшее время 12,219 1,390 29,283, поэтому вы можете видеть, что прошедшее время намного больше, чем время обработки (нехорошо). Но если я использую кластер в двух местах я получаю, что пользовательская система истекла 11,029 1,763 71,022 Использование только mapply дает истекшее время 5-7 секунд.   -  person Jason Whyte    schedule 05.12.2016
comment
Что будет, если использовать mcmapply(multi.phase.comp, input, x, y, mc.cores=no_cores) (без всяких makeCluster, stopCluster)?   -  person Steve Weston    schedule 05.12.2016
comment
@SteveWeston, 10 запусков блока makeCluster дают истекшее время от 0,492 до 0,698 секунды, а истекшее время всегда значительно больше, чем сумма пользовательского и системного времени. 10 прогонов блока с использованием mcmapply, как вы предлагаете, дают истекшее время от 0,126 до 0,219 с, и это часто меньше, чем общее время пользователя и системы. Кажется, вы что-то здесь делаете, спасибо! Поскольку я использовал makeCluster и stopCluster только один раз (запуск и остановка кластера после каждого вызова функции вызывали остановку процессов), я не ожидал большой разницы, вызванной mcmapply.   -  person Jason Whyte    schedule 05.12.2016
comment
@SteveWeston, использование моего старого ноутбука дома для запуска моего фактического кода (поэтому тайминги несопоставимы с показанными в моем последнем комментарии выше), который использует только mapply: пользовательская система истекла 12,367 0,352 16,773. Замена одного экземпляра mapply на mcmapply дает: пользовательская система истекла 28,964 86,500 118,165. Кажется, один mcmapply не может мне помочь.   -  person Jason Whyte    schedule 06.12.2016
comment
Я бы проверил, достаточно ли у вас памяти для поддержки запускаемых вами рабочих.   -  person Steve Weston    schedule 06.12.2016
comment
@SteveWeston Я только что запустил код на многоядерной машине, заменив один mapply на mcmapply, как вы предложили:   -  person Jason Whyte    schedule 08.12.2016
comment
Результаты: пользовательская система истекла 21,358 37,604 22,440, что истекло примерно в 3-4 раза медленнее, чем исходный код. Использование top для мониторинга рабочих показывает, что их %CPU принимает значения от 1 до 4 (большую часть времени процессы отображаются как Zombie), а %MEM показывает 0.0. Я интерпретирую это как означающее, что нехватка памяти не является проблемой. Возможно, задачи, которые я пытаюсь распараллелить, не поддаются такому обращению. Или стоит поиграться с кластером сокетов?   -  person Jason Whyte    schedule 08.12.2016


Ответы (1)


Параллельный пакет плохо работает для такого типа мелкозернистых параллельных вычислений, главным образом потому, что в качестве рабочих процессов он использует процессы, а не потоки. Однако если у вас очень много мелких задач (например, миллион, а не сотня), вы можете использовать методы фрагментации для повышения производительности.

Вот пример использования mclapply:

# return list of arguments to seq function
slices <- function(n, chunks) {
  if (n <= 0) return(list())
  chunks <- min(chunks, n)
  m <- n %/% chunks
  length.out <- rep(m, chunks)
  r <- n - m * chunks
  length.out[seq_len(r)] <- length.out[seq_len(r)] + 1
  from <- cumsum(c(1, length.out[-chunks]))
  mapply(c, from, length.out, SIMPLIFY=FALSE)
}

# compute result for a segment of the input vectors
wraptask <- function(il) {
  i <- seq(il[1], length.out=il[2])
  mapply(multi.phase.comp, input[i], x[i], y[i])
}

system.time({ 
  out3 <- do.call('c',
                  mclapply(slices(length(x), no_cores),
                           wraptask, mc.cores=no_cores))
})

Когда я запускал это на вашем примере, но используя входные векторы с миллионом элементов, mapply занял 5,8 секунды, а оптимизированный mclapply — 2,1 секунды. В некотором смысле это впечатляет, но многие могут подумать, что для экономии 3 секунд времени вычислений нужно проделать большую работу.

person Steve Weston    schedule 11.12.2016
comment
ах Максим Уве: Компьютеры дешевы, а думать больно. Я все еще впечатлен выигрышем и могу посмотреть, смогу ли я применить это мышление к исходной проблеме и коду. Спасибо! - person Jason Whyte; 13.12.2016