F# 普通递归与尾递归之间的效率区别

从上篇文章中,我们了解到为了防止overflow的异常,我们可以使用Continuation,上篇文章也告诉了我们如何将普通递归转换为Continuation,理所当然的,我们会考虑到一个问题:为什么不直接用Continuation呢,或者说,数据量小的时候,我们该作何选择?

当看完上篇文章之后,我也想过用最为简单的Fibonacci作为例子,来一次普通递归到CSP的转换,以下我们先来看看普通递归的Fibonacci实现:

View Code
1    module FibRecursive = 
2         let rec fib n = 
3             match n with 
4             | 1 
5             | 2 -> 1 
6             | _ -> ( fib (n-1) ) + (fib (n-2) ) 
7 
8         fib 30

使用该文章的方式,我们得到如下CSP实现结果,代码前不久我也已经放到fssnip中:

View Code
 1 module FibContinuation = 
 2     let rec fib n count = 
 3         match n with 
 4         | 1 
 5         | 2 -> count (1) 
 6         | _ ->  
 7             let first x = 
 8                 let second y = 
 9                     count(x + y) 
10                 fib (n-2) second 
11             fib (n-1) first 

由于这次测试的数据并不大,所以打开任务管理器并没有发现运行时两者的区别有多大,于是,我选择使用stopwatch来看看它们彼此的运行时间,将代码放入stopwatch中,运行后,我得到了如下结果:

首先是普通递归的结果如下:

F# 普通递归与尾递归之间的效率区别_第1张图片

接下来是CSP的结果如下:

F# 普通递归与尾递归之间的效率区别_第2张图片

当然,你也可以尝试着多用一些数据来证明这么一个结论,鉴于篇幅问题,本文只用这一组数据进行解释,因此,从上面两幅图来看,在数据量小到普通递归能承受的范围时,普通递归的效率会高于CSP的方式。

番外篇

当你首次在F#中使用尾递归时,你可能会发现一件很奇怪的事情,明明文章说CSP方式不会有异常,可是在VS editor里面选择F5或者Ctrl+F5的方式运行代码时,你依然会看到StackOverflowException,即使你的数据真的很小,普通递归都没有异常的情况下,CSP居然抛出了异常,如下图所示:

F# 普通递归与尾递归之间的效率区别_第3张图片

这不禁让我们倒吸了一口凉气,为什么会这样呢?可是,当你试着使用FSI或者fsc.exe的方式又会发现代码能够正常运行,这莫非是VS的bug么?当我请教在微软从事F#语言多年的刘涛时,他告诉我,其实不然,只是,当VS设定在Debug模式下,尾递归的功能默认是关闭的,所以当你要使用尾递归的时候,你可以将VS设定在Release的模式,或者,你也可以在Debug模式下打开尾递归的功能:

  1. 在项目管理器中对选定项目右键,选择“Properties”
      2. 打开“Build”选项卡
      3. 确保“Debug” 配置是选中的状态
      4. 将“Generate tail calls”复选框选中,如下图所示:

F# 普通递归与尾递归之间的效率区别_第4张图片

好吧,接下来你可以再试着运行你的CSP程序,相信你会看到令你满意的结果的~祝愿你F#旅程愉快!

这篇文章几天前我写在了blogspot中,一个原因是国内网络不容易访问到,还有一个原因是该文章为英文,因此写成中文版放在这里,希望对大家有一定的帮助。同时,有兴趣的朋友可以点击以下链接进行原文访问:http://jeallynduan.blogspot.com/2012/11/why-continuation-function-also-throw.html    http://jeallynduan.blogspot.com/2012/11/different-efficiency-between-tail.html

你可能感兴趣的:(F#)