/ / Проблеми с използване на паралелизация на foreach - r, foreach, паралелна обработка

Проблеми при използване на паралелизация foreach - r, foreach, паралелна обработка

Опитвам се да сравня паралелизационните опции. По-конкретно, сравнявам стандарта 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, Някаква идея защо?

Благодаря, Чарли

Отговори:

4 за отговор № 1

Като начало бихте могли да напишете кода си 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 всъщност е първоначално разработен да работи върху клъстери повече, отколкото на единични работни станции, като multicore е.


5 за отговор № 2

Да следваме нещо, което Йорис каза: 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))
}

Обзалагам се, че ще откриете, че предсказанието върви доста добре.

Също така имайте предвид използването на replicate() за този вид приложение, а не саппропил. Всъщност, foreach пакет има подобна функция за удобство, times(), което може да се приложи в този случай. Разбира се, ако кодът ви не прави обикновени симулации с идентични параметри всеки път, ще ви трябва sapply() и foreach().