Попытка начать работу с doParallel и foreach, но никакого улучшения

Я пытаюсь использовать пакет doParallel и foreach, но я получаю снижение производительности, используя пример начальной загрузки в приведенном здесь руководстве CRANpage.

library(doParallel)
library(foreach)
registerDoParallel(3)
x <- iris[which(iris[,5] != "setosa"), c(1,5)]
trials <- 10000
ptime <- system.time({
  r <- foreach(icount(trials), .combine=cbind) %dopar% {
    ind <- sample(100, 100, replace=TRUE)
    result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
    coefficients(result1)
    }
  })[3]
ptime

В этом примере возвращается 56.87.

Когда я меняю dopar на просто do, чтобы запускать его последовательно, а не параллельно, он возвращает 36.65.

Если я делаю registerDoParallel(6), он получает параллельное время до 42.11, но все еще медленнее, чем последовательно. registerDoParallel(8) получает 40.31 еще хуже, чем последовательный.

Если я увеличиваю trials до 100000, тогда последовательный прогон займет 417.16, а параллельный запуск с 3 рабочими принимает 597.31. С шестью рабочими параллельно он принимает 425.85.

Моя система

  • Dell Optiplex 990

  • Windows 7 Professional 64-разрядный

  • ОЗУ 16 ГБ

  • Intel i-7-2600 3.6GHz Quad-core с гиперпотоком

Я делаю что-то неправильно здесь? Если я сделаю самую надуманную вещь, о которой я могу думать (заменяя вычислительный код на Sys.sleep(1)), то я получаю фактическое сокращение, тесно соразмерное числу рабочих. Мне остается задаться вопросом, почему пример в руководстве снижает производительность для меня, а для них это ускользало?

Ответ 1

Основная проблема заключается в том, что doParallel выполняет attach для каждого выполнения задачи для рабочих кластера PSOCK, чтобы добавить экспортированные переменные в путь поиска пакетов. Это решает различные проблемы, связанные с определением области охвата, но может значительно ухудшить производительность, особенно с задачами с малой продолжительностью и большими объемами экспортируемых данных. Этого не происходит в Linux и Mac OS X с вашим примером, поскольку они будут использовать mclapply, а не clusterApplyLB, но это произойдет на всех платформах, если вы явно зарегистрируете кластер PSOCK.

Я считаю, что я выяснил, как решить проблемы с заданием задачи по-другому, что не повредит производительности, и я работаю с Revolution Analytics, чтобы получить исправление в следующей версии doParallel и doSNOW, который также имеет ту же проблему.

Вы можете обойти эту проблему, используя разбиение задач:

ptime2 <- system.time({
  chunks <- getDoParWorkers()
  r <- foreach(n=idiv(trials, chunks=chunks), .combine='cbind') %dopar% {
    y <- lapply(seq_len(n), function(i) {
      ind <- sample(100, 100, replace=TRUE)
      result1 <- glm(x[ind,2]~x[ind,1], family=binomial(logit))
      coefficients(result1)
    })
    do.call('cbind', y)
  }
})[3]

Это приводит только к одной задаче для каждого работника, поэтому каждый рабочий выполняется только attach один раз, а не trials / 3 раз. Это также приводит к меньшему количеству операций с большим количеством сокетов, которые могут быть выполнены более эффективно для большинства систем, но в этом случае критическая проблема - attach.