• 探索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 TailRecursion(int x)
        {
            Console.WriteLine(x);
            if (x == 10)
                return;
            TailRecursion(x + 1);
        }
        TailRecursion(0);

    使用尾递归堆栈可以不用保存上次的函数返回地址/各种状态值,而方法遗留在堆栈上的数据完全可以释放掉,这是尾递归优化的核心思想。

    由于尾递归期间,堆栈是可以释放/再利用的,也就解决递归过深而引起的溢出问题,这也是尾递归的优势所在。

    编译器优化

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

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

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

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

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

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

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

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

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

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

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

    let TailRecursion2 a cont = cont (a + 1)  

    优化成:

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

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

    总结

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

    参考资料

    http://volgarev.me/blog/62412678347

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

  • 相关阅读:
    使用客户端模型编程处理外部列表
    在 SharePoint2010 中使用 XML 查看器取得其他站点的内容列表
    在 SharePoint2010 中编程方式使用 BCS
    使用 zip 压缩包手动安装 Orchard
    NuGet Action Plan 更新到 1.1, 设置自动更新,获取 NuGet 包浏览器
    Percentage Closer Filtering
    Direct3D轮回:游戏特效之全屏泛光(Bloom)
    高斯模糊
    Shadow Map
    像素级动态模糊(Pixel Motion Blur)
  • 原文地址:https://www.cnblogs.com/mushroom/p/4311952.html
Copyright © 2020-2023  润新知