Проблемы с использованием foreach распараллеливания - PullRequest
10 голосов
/ 15 февраля 2011

Я пытаюсь сравнить варианты распараллеливания.В частности, я сравниваю стандартные реализации SNOW и mulitcore с теми, которые используют doSNOW или doMC и foreach.В качестве примера задачи я иллюстрирую центральную предельную теорему, многократно вычисляя средние значения выборок из стандартного нормального распределения.Вот стандартный код:

CltSim <- function(nSims=1000, size=100, mu=0, sigma=1){
  sapply(1:nSims, function(x){
    mean(rnorm(n=size, mean=mu, sd=sigma))
  })
}

Вот реализация SNOW:

library(snow)
cl <- makeCluster(2)

ParCltSim <- function(cluster, nSims=1000, size=100, mu=0, sigma=1){
  parSapply(cluster, 1:nSims, function(x){
    mean(rnorm(n=size, mean=mu, sd=sigma))
  })
}

Далее, метод doSNOW:

library(foreach)
library(doSNOW)
registerDoSNOW(cl)

FECltSim <- function(nSims=1000, size=100, mu=0, sigma=1) {
  x <- numeric(nSims)
  foreach(i=1:nSims, .combine=cbind) %dopar% {
    x[i] <- mean(rnorm(n=size, mean=mu, sd=sigma))
  }
}

Я получаюследующие результаты:

> system.time(CltSim(nSims=10000, size=100))
   user  system elapsed 
  0.476   0.008   0.484 
> system.time(ParCltSim(cluster=cl, nSims=10000, size=100))
   user  system elapsed 
  0.028   0.004   0.375 
> system.time(FECltSim(nSims=10000, size=100))
   user  system elapsed 
  8.865   0.408  11.309 

Реализация SNOW экономит около 23% вычислительного времени по сравнению с непараллельным прогоном (экономия времени увеличивается по мере увеличения числа симуляций, как и следовало ожидать).Попытка foreach фактически увеличивает время выполнения в 20 раз. Кроме того, если я изменяю %dopar% на %do% и проверяю непараллельную версию цикла, это занимает более 7 секунд.

Кроме того, мы можем рассмотреть пакет multicore.Симуляция, написанная для multicore:

library(multicore)
MCCltSim <- function(nSims=1000, size=100, mu=0, sigma=1){
  unlist(mclapply(1:nSims, function(x){
    mean(rnorm(n=size, mean=mu, sd=sigma))
  }))
}

Мы получаем улучшение скорости еще лучше, чем SNOW:

> system.time(MCCltSim(nSims=10000, size=100))
   user  system elapsed 
  0.924   0.032   0.307 

Начиная новую сессию R, мы можем попытаться foreach реализация, использующая doMC вместо doSNOW, вызывающая

library(doMC)
registerDoMC()

, затем выполняющая FECltSim(), как указано выше, все еще находящая

> system.time(FECltSim(nSims=10000, size=100))
   user  system elapsed 
  6.800   0.024   6.887 

Это "только" в 14 разувеличение по сравнению с непараллельным временем выполнения.

Вывод: Мой код foreach не работает эффективно ни при doSNOW, ни doMC.Есть идеи, почему?

Спасибо, Чарли

Ответы [ 2 ]

5 голосов
/ 17 февраля 2011

Если следовать тому, что сказал Джорис, foreach() лучше всего, когда количество заданий не превышает количество процессоров, которые вы будете использовать.Или, в более общем смысле, когда каждая работа занимает значительное количество времени сама по себе (скажем, секунды или минуты).Создание потоков сопряжено с большими затратами, поэтому вы действительно не хотите использовать его для множества небольших работ.Если вы выполняли 10 миллионов симов, а не 10 тысяч, и вы структурировали свой код следующим образом:

nSims = 1e7
nBatch = 1e6
foreach(i=1:(nSims/nBatch), .combine=c) %dopar% {
  replicate(nBatch, mean(rnorm(n=size, mean=mu, sd=sigma))
}

Могу поспорить, вы обнаружите, что foreach работает довольно хорошо.использование replicate() для такого рода приложений, а не для sapply.На самом деле, пакет foreach имеет аналогичную вспомогательную функцию times(), которая может быть применена в этом случае.Конечно, если ваш код не выполняет простое моделирование с одинаковыми параметрами каждый раз, вам понадобятся sapply() и foreach().

4 голосов
/ 16 февраля 2011

Начнем с того, что вы могли бы написать свой код foreach немного более кратко:

FECltSim <- function(nSims=1000, size=100, mu=0, sigma=1) {
  foreach(i=1:nSims, .combine=c) %dopar% {
    mean(rnorm(n=size, mean=mu, sd=sigma))
  }
}

Это дает вам вектор, нет необходимости явно делать его внутри цикла.Также не нужно использовать cbind, так как каждый раз вы получаете только одно число.Так что .combine=c будет делать

С foreach дело в том, что он создает довольно много служебной информации для связи между ядрами и согласования результатов различных ядер.Быстрый взгляд на профиль показывает это довольно четко:

$by.self
                         self.time self.pct total.time total.pct
$                             5.46    41.30       5.46     41.30
$<-                           0.76     5.75       0.76      5.75
.Call                         0.76     5.75       0.76      5.75
...

Более 40% времени занято выбором вещей.Он также использует множество других функций для всей операции.На самом деле, foreach рекомендуется только в том случае, если у вас относительно мало раундов с очень трудоемкими функциями.

Два других решения построены на другой технологии и гораздо менее эффективны в R. На sidenode snow изначально изначально разрабатывался для работы на кластерах больше, чем на отдельных рабочих станциях, как * 1014.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...