探索c#之尾递归编译器优化_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > 探索c#之尾递归编译器优化

探索c#之尾递归编译器优化

 2015/3/16 9:40:38  蘑菇先生  程序员俱乐部  我要评论(0)
  • 摘要:阅读目录:递归运用尾递归优化编译器优化递归运用一个函数直接或间接的调用自身,这个函数即可叫做递归函数。递归主要功能是把问题转换成较小规模的子问题,以子问题的解去逐渐逼近最终结果。递归最重要的是边界条件,这个边界是整个递归的终止条件。staticintRecFact(intx){if(x==0)return1;returnx*RecFact(x-1);}RecFact(10);上面是个经典阶乘函数的实现。这里分2步:转换,把10的阶乘转化成10*9!,10(9*8!)...
  • 标签:编译器 C# 编译 优化 递归

阅读目录:

  1. 递归运用
  2. 尾递归优化
  3. 编译器优化

递归运用

一个函数直接或间接的调用自身,这个函数即可叫做递归函数。

  • 递归主要功能是把问题转换成较小规模的子问题,以子问题的解去逐渐逼近最终结果。
  • 递归最重要的是边界条件,这个边界是整个递归的终止条件。

static int RecFact(int x)
{
    if (x == 0)
        return 1;
    return x * RecFact(x - 1);
}
RecFact(10);

上面是个经典阶乘函数的实现。这里分2步:

  • 转换,把10的阶乘转化成10*9!,10(9*8!)....每次转换规模就变的更小。
  • 逼近,转化到最小规模时0!,求解1。开始逆向逐渐逼近到10,得出解。

这里的x==0就是我们的边界条件(即终止条件),也有的依赖外部变量为边界。

如果一个递归函数没有边界,也就无法停止(无限循环内存溢出),当然这样也没什么意义

RecFact调用堆栈

常见使用场景:

  • 阶乘/斐波那契数列/汉诺塔
  • 遍历硬盘文件
  • InnerExceptions异常扑捉(exception.InnerException==null)

尾递归优化

当边界不明确的时候,递归就很容易出现溢出问题。

在阶乘过程中,堆栈需要保存每次(RecFact)调用的返回地址及当时所有的局部变量状态,期间堆栈空间是无法释放的(即容易出现溢出)。

为了优化堆栈占用问题,从而提出尾递归优化的办法。

    static void Recurse(int x)
    {
        Console.WriteLine(x);
        if (x == 10)
            return;
        Recurse(x + 1);
    }
    Recurse(0);

上面这个递归和阶乘那个区别在于没有返回值,也就是说堆栈可以不用保存上一次的返回地址/状态值,这就是尾递归优化的思想。

尾递归可以解决递归过深而引起的溢出问题,因为递归期间堆栈是可以释放/再利用的。

编译器优化

尾递归优化,看起来是蛮美好的,但在net中却有点乱糟糟的感觉。

  • Net在C#语言中是JIT编译成汇编时进行优化的。
  • Net在IL上,有个特殊指令tail去实现尾递归优化的(F#中)。

我们执行 Recurse(0)(x==1000000) 得出如下结论:

C#/64位/Release是有JIT编译器进行尾递归优化的(非C#编译器优化)。

C#/32位或C#/Debug模式中JIT是不进行优化的。

F#在优化尾递归也分2种情况:

1、 简单的尾递归优化成while循环,如下:

 let rec Recurse(x) =
  if (x = 1000) then true
  else Recurse(x + 1)

(方便演示C#呈现)优化成:

    public static bool Recurse(int x)
    {
        while (x != 0x3e8)
        {
            x++;
        }
        return true;
    }

2、 复杂的尾递归,F#编译会生成IL指令Tail进行优化,如下:

let Recurse2 a cont = cont (a + 1)  

优化成:

如何定义复杂的尾递归呢?通常是后继传递模式(CPS)。

F#中在debug模式下,需要在编译时配置:

总结

在C#语言(过程式编程思想)中,优先考虑的是循环,而不是递归/尾递归。 但在函数式编程思想当中,递归/尾递归使用则是主流用法,就像在C#使用循环一样。

参考资料

http://volgarev.me/blog/62412678347

http://stackoverflow.com/questions/15864670/generate-tail-call-opcode

发表评论
用户名: 匿名