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