Pourquoi ma fonction récursive est si lente en R?

ce qui suit prend environ 30 secondes à courir alors que je m'attendrais à ce qu'il soit presque instantané. Il y a un problème avec mon code?

x <- fibonacci(35);

fibonacci <- function(seq) {
    if (seq == 1) return(1);
    if (seq == 2) return(2);
    return (fibonacci(seq - 1) + fibonacci(seq - 2));
}
18
demandé sur TMS 2011-07-24 17:33:50

7 réponses

Patrick Burns donne un exemple en R Inferno d'une manière de faire de memoization dans R local() et <<-. En fait, c'est un de fibonacci:

fibonacci <- local({
    memo <- c(1, 1, rep(NA, 100))
    f <- function(x) {
        if(x == 0) return(0)
        if(x < 0) return(NA)
        if(x > length(memo))
        stop("’x’ too big for implementation")
        if(!is.na(memo[x])) return(memo[x])
        ans <- f(x-2) + f(x-1)
        memo[x] <<- ans
        ans
    }
})
26
répondu Matthew Plourde 2011-07-24 15:46:46

Que simplement fourni une belle occasion de se brancher Rcpp ce qui nous permet d'ajouter des fonctions C++ facilement à R.

donc après avoir légèrement corrigé votre code, et en utilisant les paquets inline (pour compiler, charger et relier facilement les courts extraits de code en tant que fonctions chargeables dynamiquement) ainsi que rbenchmark temps et des fonctions de comparaison, nous nous retrouvons avec un superbe augmentation de 700 fois dans la performance:

R> print(res)
        test replications elapsed relative user.self sys.self
2 fibRcpp(N)            1   0.092    1.000      0.10        0
1    fibR(N)            1  65.693  714.054     65.66        0
R> 

ici nous voir temps écoulé de 92 millisecondes par rapport à 65 secondes, pour un rapport relatif de 714. Mais maintenant tout le monde t'a dit de ne pas faire ça directement en R.... Le code est ci-dessous.

## inline to compile, load and link the C++ code
require(inline)

## we need a pure C/C++ function as the generated function
## will have a random identifier at the C++ level preventing
## us from direct recursive calls
incltxt <- '
int fibonacci(const int x) {
   if (x == 0) return(0);
   if (x == 1) return(1);
   return (fibonacci(x - 1)) + fibonacci(x - 2);
}'

## now use the snipped above as well as one argument conversion
## in as well as out to provide Fibonacci numbers via C++
fibRcpp <- cxxfunction(signature(xs="int"),
                   plugin="Rcpp",
                   incl=incltxt,
                   body='
   int x = Rcpp::as<int>(xs);
   return Rcpp::wrap( fibonacci(x) );
')

## for comparison, the original (but repaired with 0/1 offsets)
fibR <- function(seq) {
    if (seq == 0) return(0);
    if (seq == 1) return(1);
    return (fibR(seq - 1) + fibR(seq - 2));
}

## load rbenchmark to compare
library(rbenchmark)

N <- 35     ## same parameter as original post
res <- benchmark(fibR(N),
                 fibRcpp(N),
                 columns=c("test", "replications", "elapsed",
                           "relative", "user.self", "sys.self"),
                 order="relative",
                 replications=1)
print(res)  ## show result

Et pour être complet, les fonctions aussi produire le bon de sortie:

R> sapply(1:10, fibR)
 [1]  1  1  2  3  5  8 13 21 34 55
R> sapply(1:10, fibRcpp)
 [1]  1  1  2  3  5  8 13 21 34 55
R> 
25
répondu Dirk Eddelbuettel 2011-07-24 17:51:10

parce que vous utilisez un des le pire des algorithmes dans le monde!

dont la complexité est O(fibonacci(n))= O((golden ratio)^n) et golden ratio is 1.6180339887498948482…

14
répondu Pratik Deoghare 2011-07-24 14:47:38

:-) parce que vous utilisez l'algorithme exponentiel!!! Donc, pour fibonacci numéro N il doit appeler la fonction 2^N fois, qui 2^35, qui est diable d'un nombre.... : -)

utiliser l'algorithme linéaire:

fib = function (x)
{
        if (x == 0)
                return (0)
        n1 = 0
        n2 = 1
        for (i in 1:(x-1)) {
                sum = n1 + n2
                n1 = n2
                n2 = sum
        }
        n2
}

Désolé, edit: la complexité de l'algorithme récursif n'est pas O(2^N), mais O(fib(N)), Martinho Fernandes grandement plaisanté :-) Vraiment une bonne note :-)

14
répondu TMS 2018-06-08 06:13:58

parce que le memoise paquet a déjà été mentionné voici une implémentation de référence:

fib <- function(n) {
  if (n < 2) return(1)
  fib(n - 2) + fib(n - 1)
}
system.time(fib(35))
##    user  system elapsed 
##   36.10    0.02   36.16

library(memoise)
fib2 <- memoise(function(n) {
  if (n < 2) return(1)
  fib2(n - 2) + fib2(n - 1)
})
system.time(fib2(35))
##    user  system elapsed 
##       0       0       0

Source: Wickham, H.: Advanced R, p. 538.

En général memoization en informatique signifie que vous enregistrez les résultats d'une fonction de sorte que lorsque vous l'appelez de nouveau avec les mêmes arguments, il renvoie la valeur enregistrée.

5
répondu vonjd 2016-06-10 10:04:05

récursive de la mise en œuvre linéaires coût:

fib3 <- function(n){
  fib <- function(n, fibm1, fibm2){
    if(n==1){return(fibm2)}
    if(n==2){return(fibm1)}
    if(n >2){
      fib(n-1, fibm1+fibm2, fibm1)  
    }
  }
fib(n, 1, 0)  
}

en Comparant avec la solution récursive avec un coût exponentiel:

> system.time(fibonacci(35))
  usuário   sistema decorrido 
   14.629     0.017    14.644 
> system.time(fib3(35))
  usuário   sistema decorrido 
    0.001     0.000     0.000

Cette solution peut être vectorisée avec ifelse:

fib4 <- function(n){
    fib <- function(n, fibm1, fibm2){
        ifelse(n<=1, fibm2,
          ifelse(n==2, fibm1,
            Recall(n-1, fibm1+fibm2, fibm1)  
          ))
    }
    fib(n, 1, 0)  
}

fib4(1:30)
##  [1]      0      1      1      2      3      5      8
##  [8]     13     21     34     55     89    144    233
## [15]    377    610    987   1597   2584   4181   6765
## [22]  10946  17711  28657  46368  75025 121393 196418
## [29] 317811 514229

les seuls changements nécessaires sont ==<= pour les n==1 cas, et changent à chaque if bloc à l'équivalent ifelse.

4
répondu Carlos Cinelli 2014-08-24 05:02:07

si vous cherchez vraiment à retourner les nombres Fibonacci et que vous n'utilisez pas cet exemple pour explorer comment fonctionne la récursion, alors vous pouvez le résoudre de façon non-récursive en utilisant ce qui suit:

fib = function(n) {round((1.61803398875^n+0.61803398875^n)/sqrt(5))}
2
répondu Michael Bean 2013-06-17 14:44:54