Почему foreach() %do% иногда медленнее, чем for?
Я играю с параллелизацией в R впервые. В качестве первого игрушечного примера я попробовал
library(doMC)
registerDoMC()
B<-10000
myFunc<-function()
{
for(i in 1:B) sqrt(i)
}
myFunc2<-function()
{
foreach(i = 1:B) %do% sqrt(i)
}
myParFunc<-function()
{
foreach(i = 1:B) %dopar% sqrt(i)
}
я знаю это sqrt()
выполняется слишком быстро, чтобы распараллеливание имело значение, но я не ожидал, что foreach() %do%
будет медленнее, чем for()
:
> system.time(myFunc())
user system elapsed
0.004 0.000 0.005
> system.time(myFunc2())
user system elapsed
6.756 0.000 6.759
> system.time(myParFunc())
user system elapsed
6.140 0.524 6.096
В большинстве примеров, которые я видел, foreach() %dopar%
по сравнению с foreach() %do%
скорее, чем for()
, поскольку foreach() %do%
был намного медленнее, чем for()
в моем примере с игрушкой я сейчас немного растерялся. Почему-то я думал, что это были эквивалентные способы построения циклов for. В чем разница? Они когда-нибудь эквивалентны? Является foreach() %do%
всегда медленнее?
ОБНОВЛЕНИЕ: После ответа @Peter Fines я обновляю myFunc
следующее:
a<-rep(NA,B)
myFunc<-function()
{
for(i in 1:B) a[i]<-sqrt(i)
}
Это делает for()
немного медленнее, но не намного
> system.time(myFunc())
user system elapsed
0.036 0.000 0.035
> system.time(myFunc2())
user system elapsed
6.380 0.000 6.385
1 ответ
for
побежит sqrt
B раз, по-видимому, отбрасывая ответ каждый раз. foreach
однако возвращает список, содержащий результат каждого выполнения тела цикла. Это может привести к значительным дополнительным расходам независимо от того, работает ли он в параллельном или последовательном режиме (%dopar%
или же %do%
).
Я основал свой ответ, выполнив следующий код, который, как представляется, подтверждается виньеткой foreach, в которой говорится, что foreach отличается от цикла for тем, что его возвращение является списком значений, тогда как цикл for не имеет значения и использует побочные эффекты. передать свой результат."
> print(for(i in 1:10) sqrt(i))
NULL
> print(foreach(i = 1:10) %do% sqrt(i))
[[1]]
[1] 1
[[2]]
[1] 1.414214
[[3]]
... etc
ОБНОВЛЕНИЕ: из вашего обновленного вопроса я вижу, что приведенного выше ответа недостаточно для учета разницы в производительности. Поэтому я посмотрел на исходный код foreach
и можно увидеть, что происходит много! Я не пытался понять, как именно это работает, однако do.R
а также foreach.R
показать, что даже когда %do%
работает, большие части foreach
конфигурация все еще выполняется, что имеет смысл, если, возможно, %do%
опция в основном предоставляется, чтобы позволить вам проверить foreach
код без необходимости конфигурировать и загружать параллельный бэкэнд. Он также должен поддерживать более продвинутые средства размещения и итерации, которые foreach
обеспечивает.
В коде есть ссылки на кэширование результатов, проверку ошибок, отладку и создание локальных переменных среды для аргументов каждой итерации (см. Функцию doSEQ
в do.R
например). Я предполагаю, что именно это создает разницу, которую вы наблюдали. Конечно, если вы выполняете намного более сложный код внутри цикла (это на самом деле выиграет от структуры параллелизации, такой как foreach
), эти накладные расходы станут неактуальными по сравнению с теми преимуществами, которые они обеспечивают.