Почему R для цикла в 10 раз медленнее, чем при использовании foreach?

Это действительно раздувает мой разум. Основной цикл занимает 8 секунд на моем компьютере:

system.time({
x <- 0
for (p in 1:2) {
    for (i in 1:500) {
        for (j in 1:5000) {
            x <- x + i * j
        }
    }
}
})
x

Если я использую foreach в непараллельном режиме, это займет всего 0,7 секунды!!!

system.time({
x <- 0
foreach(p = 1:2, .combine = rbind) %do% 
    for (i in 1:500) {
        for (j in 1:5000) {
            x <- x + i * j
        }
    }
})
x

Результат тот же, но foreach как-то мог достичь его намного быстрее, чем базовый R! Где неэффективность основного R?

Как это возможно?

Фактически, я получил полный противоположный результат по сравнению с этим: Почему foreach()% do% иногда медленнее, чем для?

Ответы

Ответ 1

foreach При использовании последовательно в конечном итоге использует compiler для создания скомпилированного байтового кода с использованием неэкспортируемых функций make.codeBuf и cmp. Вы можете использовать cmpfun для компиляции innerloop в байт-код для имитации этого и достижения аналогичного ускорения.

f.original <- function() {
x <- 0
for (p in 1:2) {
    for (i in 1:500) {
        for (j in 1:5000) {
            x <- x + i * j
        }
    }
}
x
}

f.foreach <- function() {
x <- 0
foreach(p = 1:2, .combine = rbind) %do% 
    for (i in 1:500) {
        for (j in 1:5000) {
            x <- x + i * j
        }
    }
x
}

f.cmpfun <- function(x) {
f <- cmpfun(function(x) {
    for (i in 1:500) {
        for (j in 1:5000) {
            x <- x + i * j
            }
        }
        x
    })
    f(f(0))
}

Результаты

library(microbenchmark)
microbenchmark(f.original(),f.foreach(),f.cmpfun(), times=5)
Unit: milliseconds
         expr       min        lq    median        uq       max neval
 f.original() 4033.6114 4051.5422 4061.7211 4072.6700 4079.0338     5
  f.foreach()  426.0977  429.6853  434.0246  437.0178  447.9809     5
   f.cmpfun()  418.2016  427.9036  441.7873  444.1142  444.4260     5
all.equal(f.original(),f.foreach(),f.cmpfun())
[1] TRUE