foreach очень медленный с большим количеством значений

Я пытаюсь использовать foreach для параллельных вычислений. Он отлично работает, если нужно перебрать небольшое количество значений, но в какой-то момент он становится невероятно медленным. Вот простой пример:

library(foreach)
library(doParallel)

registerDoParallel(8)

out1 <- foreach(idx=1:1e6) %do%
    {
        1+1
    }

out2 <- foreach(idx=1:1e6) %dopar%
    {
        1+1
    }

out3 <- mclapply(1:1e6,
                 function(x) 1+1,
                 mc.cores=20)

out1 и out2 выполняются невероятно долго. Ни один из них даже не порождает несколько потоков, пока я продолжаю их работу. out3 создает потоки почти сразу и работает очень быстро. Выполняет ли foreach какую-то начальную обработку, которая плохо масштабируется? Если да, то есть ли простое решение? Я действительно предпочитаю синтаксис foreach.

Я также должен отметить, что реальный код, который я пытаюсь распараллелить, существенно сложнее, чем 1+1. Я показываю это только в качестве примера, потому что даже с этим простым кодом foreach выполняет предварительную обработку, которая невероятно медленная.


person Rob Richmond    schedule 11.10.2018    source источник
comment
Я не могу ответить, как использовать для этого foreach, но могу предложить использовать пакет future.apply. Вы настраиваете его с помощью library(future.apply); plan(multiprocess(workers = 3)), а затем можете выполнить свою функцию с помощью: future_sapply(X = 1:1e6, FUN = function(x) {1 + 1})   -  person bschneidr    schedule 12.10.2018


Ответы (1)


виньетка forach/doParallel говорит (для кода, который намного меньше вашего):

Обратите внимание, что это не практическое использование doParallel. Это наша программа «Hello, world» для параллельных вычислений. Он проверяет, все ли установлено и настроено правильно, но не ожидайте, что он будет работать быстрее, чем последовательный цикл for, потому что это не так! sqrt выполняется слишком быстро, чтобы его можно было выполнять параллельно, даже при большом количестве итераций. При небольших задачах накладные расходы на планирование задачи и возврат результата могут превышать время выполнения самой задачи, что приводит к низкой производительности. Кроме того, в этом примере не используются векторные возможности sqrt, необходимые для получения достойной производительности. Это всего лишь тест и педагогический пример, а не эталон.

Так что может быть в характере вашей настройки, что это не быстрее.

Вместо этого попробуйте без распараллеливания, но с использованием векторизации:

q <- sapply(1:1e6, function(x) 1 + 1 )

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

x <- rep(1, n=1e6)
r <- x + 1

Он мгновенно добавляет к 1e6 1s 1. (Сила векторизации...)

Комбинация foreach с doParallel по моему личному опыту намного медленнее, чем если использовать пакет биоинформатики BiocParallel из репозитория Bioconda. (Я биоинформатик, и в биоинформатике у нас очень часто бывают сложные расчеты, поскольку нам нужно обрабатывать отдельные файлы данных размером в несколько гигабайт - и их много). Я попробовал вашу функцию с помощью BiocParallel, и она использует все назначенные процессоры на 100% (проверено запуском htop во время выполнения задания), все это заняло 17 секунд.

Конечно - с вашим легким примером это применимо:

накладные расходы на планирование задачи и возврат результата могут быть больше, чем время выполнения самой задачи

Во всяком случае, кажется, что он использует процессоры более тщательно, чем doParallel. Так что используйте это, если вам нужно выполнить сложные вычислительные задачи. Вот код, как я это сделал:

# For bioconductor packages, the best is to install this:
install.packages("BiocManager")

# Then activate the installer
require(BiocManager)

# Now, with the `install()` function in this package, you can install
# conveniently Bioconductor packages like `BiocParallel`
install("BiocParallel")

# then, activate it
require(BiocParallel)

# initiate cores:
bpparam <- bpparam <- SnowParam(workers=4, type="SOCK") # 4 or take more CPUs

# prepare the function you want to parallelize
FUN <- function(x) { 1 + 1 }

# and now you can call the function using `bplapply()`
# the loop parallelizing function in BiocParallel.
s <- bplapply(1:1e6, FUN, BPPARAM=bpparam) # each value of 1:1e6 is given to 
# FUN, note you have to pass the SOCK cluster (bpparam) for the 
# parallelization

Для получения дополнительной информации перейдите к виньетке пакета BiocParallel. . Посмотрите на bioconductor, сколько пакетов он предоставляет, и все они хорошо задокументированы. Я надеюсь, что это поможет вам в ваших будущих параллельных вычислениях.

person Gwang-Jin Kim    schedule 11.10.2018
comment
Спасибо за некоторые предложения. Фактический код, который я запускаю параллельно, представляет собой крупномасштабную оценку и определенно не может быть векторизован. Мой пример был строго иллюстрирован тем, что foreach, по-видимому, выполняет предварительную обработку, которая невероятно медленна для большого индекса. Он даже не начинает делать параллельную работу в течение достаточно долгого времени. Я тоже могу попробовать этот пакет. - person Rob Richmond; 12.10.2018
comment
@Rob Richmond: Да, я знаю, вы хотите использовать его для реальных процессов, которым потребуется больше ресурсов. Просто я объяснял, почему ваш частный пример вас разочаровывает. И да, я думаю, что с BiocParallel вы могли бы быть более довольны. - person Gwang-Jin Kim; 12.10.2018
comment
Я играл с BiocParallel и нашел, что это фантастическая альтернатива foreach. Похоже, в foreach есть что-то, что заставляет его зависать на большом количестве задач, которые остаются без ответа. Тем не менее, я приму ваш ответ, поскольку он улучшит мой общий рабочий процесс. Спасибо! - person Rob Richmond; 12.10.2018
comment
Добро пожаловать! Я рад, что смог помочь! И да, однажды в начале этого года я пробовал также doParallel и foreach - и оба тоже были разочарованы... Спасибо! - person Gwang-Jin Kim; 13.10.2018