为什么我的递归函数在R中这么慢?

del*_*ber 20 algorithm optimization recursion r fibonacci

以下大约需要30秒才能运行,而我希望它几乎是即时的.我的代码有问题吗?

x <- fibonacci(35);

fibonacci <- function(seq) {
    if (seq == 1) return(1);
    if (seq == 2) return(2);
    return (fibonacci(seq - 1) + fibonacci(seq - 2));
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*rde 28

帕特里克伯恩斯在R Inferno中举了一个例子来说明用R local()和R做记忆的方法<<-.事实上,这是一个斐波那契:

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
    }
})
Run Code Online (Sandbox Code Playgroud)


Dir*_*tel 27

这只是提供了一个插入Rcpp的好机会,它允许我们轻松地将C++函数添加到R.

所以,稍微修理你的代码,并使用包后在线(能够很容易编译,下载和链接的短代码段为可动态加载的功能),以及rbenchmark时间和比较功能,我们结束了一个令人惊叹的性能700倍:

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> 
Run Code Online (Sandbox Code Playgroud)

在这里,我们看到92毫秒的经过时间对比65秒,相对比率为714.但到现在为止,其他所有人都告诉过你不要直接在R ......中执行此操作.代码如下.

## 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
Run Code Online (Sandbox Code Playgroud)

为了完整起见,这些函数还可以生成正确的输出:

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> 
Run Code Online (Sandbox Code Playgroud)

  • 尽管如此,这是无稽之谈.任何给定的系统都会有一个特殊的弱点.我的观点是,我们可以通过结合相关优势来建立更好的系统 - 甚至可以像这个例子那样容易地做到 - 并且不要过于紧张.例如,查看钱伯斯去年秋天在斯坦福大学的演讲:总是*关于组合语言和工具.而我的拙见是,Rcpp可以帮助你将C++和R的更好部分结合起来.但是你当然可以自由地将R扔进垃圾箱并使用本周流行的任何东西.祝好运. (8认同)
  • 内联包由R驱动,因此获得标准的gcc/g ++选项.所以我称之为公平测试:)因为它向您显示编译器可以为您做的,如果您将R三线程转换为C++三线程.无论如何,如果你真的想要,你可以学习asm代码. (2认同)

TMS*_*TMS 15

:-)因为你使用指数算法!因此,对于斐波那契数N,它必须调用函数2 ^ N次,其中2 ^ 35,这是一个数字.... :-)

使用线性算法:

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
}
Run Code Online (Sandbox Code Playgroud)

对不起,编辑:指数递归算法的复杂性不是O(2 ^ N)而是O(fib(N)),正如Martinho Fernandes大肆开玩笑说的那样 :-)真是个好注意:-)


Pra*_*are 14

因为您使用的是世界上最糟糕的算法之一!

复杂性是O(fibonacci(n))= O((golden ratio)^n)golden ratio is 1.6180339887498948482…


von*_*njd 6

因为这里已经提到过memoise是一个参考实现:

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
Run Code Online (Sandbox Code Playgroud)

资料来源:Wickham,H.:Advanced R,p.238.

通常,计算机科学中的memoization意味着您保存函数的结果,这样当您使用相同的参数再次调用它时,它将返回保存的值.


Car*_*lli 5

具有线性成本的递归实现:

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)  
}
Run Code Online (Sandbox Code Playgroud)

与指数成本的递归解决方案相比:

> 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
Run Code Online (Sandbox Code Playgroud)

此解决方案可以使用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
Run Code Online (Sandbox Code Playgroud)

所需的唯一改变是改变==<=n==1的情况下,并且改变每个if块的等效ifelse.