Являются ли «автономные» функции более эффективными в R? - PullRequest
0 голосов
/ 14 января 2019

Я пишу функцию, которая должна вызывать функцию g, передаваемую в качестве параметра каждому элементу списка, итеративно.

Мне интересно, как сделать это максимально быстрым. Я могу достичь приемлемой скорости, используя Rcpp и конкретный вид g (записывая все на Cpp), но не могу понять, смогу ли я достичь аналогичной скорости, передав функцию R в качестве аргумента.

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

minus <- function(x) -x
minus_vec <- Vectorize(minus, "x")

Тестирование с некоторыми простыми функциями для инвертирования знаков.

f0 <- function(x) {
  sapply(x, minus)
}

f1 <- function(x) {
  for(i in seq_along(x)){
    x[i] <- -x[i]
  }
  x
}

f2 <- function(x) {
  for(i in seq_along(x)){
    x[i] <- minus(x[i])
  }
  x
}

Я получил следующие результаты:

a <- 1:10^5
library(rbenchmark)
benchmark(f0(a), f1(a), f2(a), minus_vec(a), minus(a))[,c(1,4)]

          test relative
1        f0(a)  454.842
2        f1(a)   25.579
3        f2(a)  178.211
4 minus_vec(a)  523.789
5     minus(a)    1.000

Я хотел бы получить некоторые пояснения по следующим пунктам:

  • Почему f1 и f2 не имеют одинаковую скорость? Написание кода -x[i] и вызов функции minus(x[i]) действительно должны быть такими разными, когда они делают одно и то же?

  • Почему f0 медленнее, чем f2? Я всегда думал, что функции apply более эффективны, чем циклы for, но никогда не понимал, почему, и теперь я даже нашел контрпример.

  • Могу ли я сделать функцию так же быстро, как f1, используя функцию minus?

  • Почему векторизация minus (ненужная, поскольку - уже векторизована, но это может быть не всегда) сделала ее настолько плохой?

Ответы [ 2 ]

0 голосов
/ 14 января 2019

Не полный ответ, но вот несколько заметок

1 minus(x) против -x: Ничто не лучше, чем что-то делать

Ваша функция minus вызывает `-`, поэтому добавленный шаг добавляет время вычисления. Я, честно говоря, не знаю, кто, что и когда конкретно, другими словами, я бы не знал, сколько еще времени вычислений следует ожидать.

Вот пример, подчеркивающий это: у нас есть четыре функции, все квадратные числа

fa <- function (n) n^2
fb <- function (n) fa(n)
fc <- function (n) fb(n)
fd <- function (n) fc(n)
Fa <- function (n) {
  for (i in seq_along(n)) n[i] <- fa(i)
  n
}
Fb <- function (n) {
  for (i in seq_along(n)) n[i] <- fb(i)
  n
}
Fc <- function (n) {
  for (i in seq_along(n)) n[i] <- fc(i)
  n
}
Fd <- function (n) {
  for (i in seq_along(n)) n[i] <- fd(i)
  n
}

А вот результаты бенчмаркинга

n <- 1:10^4
b <- benchmark(Fa(n),Fb(n),Fc(n),Fd(n), replications = 1000L)
b
#    test replications elapsed relative user.self sys.self user.child sys.child
# 1 Fa(n)         1000    3.93    1.000      3.85     0.00         NA        NA
# 2 Fb(n)         1000    7.08    1.802      6.94     0.02         NA        NA
# 3 Fc(n)         1000   10.16    2.585      9.94     0.06         NA        NA
# 4 Fd(n)         1000   13.68    3.481     13.56     0.00         NA        NA
# looks rather even
diff(b$elapsed)
# [1] 3.15 3.08 3.52

Теперь вернемся к вашей minus функции

a <- 1:10^5
b <- benchmark(f0(a), f1(a), f2(a), minus_vec(a), minus(a))          
b$elapsed[b$test == 'f2(a)'] - b$elapsed[b$test == 'f1(a)']    
# [1] 3.39   

2 apply против for против Vectorize:

@ NavyCheng предоставил хороший материал по этой теме. Теперь я понимаю, что семейство apply (как и Vectorize) зацикливается на R (тогда как, если я не ошибаюсь, зацикливание за `-` выполняется в C).

Опять же, я не знаю точных деталей, но если apply/Vectorize использовать R петли, то в теории (и часто на практике) можно написать правильную for цикл, который будет работать как хорошо или лучше.


3 A Функция так же быстро, как f1:

Ad-hoc, закрытие, которое я придумал, было изменой с использованием пакета Rcpp. ( обман , поскольку сначала пишется функция в c++)

In C ++

#include <RcppArmadillo.h>
//[[Rcpp::depends(RcppArmadillo)]]
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector minusCpp(NumericVector x) {
  for (int k = 0; k < x.length(); ++k) {
    x[k] = -x[k];
  }
  return x;
}

Теперь к bechmarks в R

a <- 1:10^5
b <- benchmark(f0(a), f1(a), f2(a), minus_vec(a), minus(a), minusCpp(a))          
b
#           test replications elapsed relative user.self sys.self user.child sys.child
# 1        f0(a)          100    9.47       NA      9.22     0.01         NA        NA
# 2        f1(a)          100    0.53       NA      0.54     0.00         NA        NA
# 3        f2(a)          100    4.23       NA      4.24     0.00         NA        NA
# 5     minus(a)          100    0.00       NA      0.00     0.00         NA        NA
# 4 minus_vec(a)          100   10.42       NA     10.39     0.02         NA        NA
# 6  minusCpp(a)          100    0.05       NA      0.04     0.00         NA        NA
0 голосов
/ 14 января 2019

Игнорировать -x [i] и минус (-x [i]) , и я суммирую четыре вопроса на два:

  • Почему применить семейство медленнее, чем forloop ?
  • Почему Векторизация медленнее, чем Применить Семейство?

По первому вопросу:

Функции apply разработаны таким образом, чтобы их было удобно и понятно читать, не обязательно быстро.

и применяются семья будет делать больше вещей, чем forloop ,

Также функция sapply сначала использует as.vector (unlist (...)) для преобразования чего-либо в вектор, а в конце пытается упростить ответ в подходящую форму.

Вы не можете прочитать здесь и здесь для более подробной информации.

Что касается второго вопроса, это потому, что Vectorize - это оболочка mapply , и если вы наберете Vectorize в Rstudio, вы увидите подробный код. Вы можете прочитать это для получения дополнительной помощи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...