• Closure中关于递归的一点补充


    在对Closure的再思考里面我提到了说网上有观点认为用lambda表达式声明的“递归”实际上并不是真正的递归。本文针对这个观点做专门的研究。

    传统的递归

    所谓传统的递归,是指一直来我们所经常使用的经典结构的递归。以n的阶乘来作为例子说吧,“传统”的递归结构可以用如下的代码表示:

    public static int FacRecursive(int n)
    {
        if (n <= 1) return 1;
        return FacRecursive(n - 1) * n;
    }
    

    对于这个结构,是没有任何问题的。FacRecursive(5)就表示为计算5的阶乘,函数自身的调用顺序和参数传递可以用下图表示:

    变相的递归

    理解了传统的递归后,再来看看如下函数所表示的意义:

    public static int Fac(int n)
    {
        if (n <= 1) return 1;
        return FacShadow(n - 1) * n;
    }
    
    public static int FacShadow(int n)
    {
        return Fac(n);
    }
    


    应该不难理解,假如从Fac函数入口,程序执行便产生了Fac和FacShadow函数之间依次互相调用的情况,其最终计算结果也是n的阶乘。对于表达式Fac(5)的函数执行调用序列可用下图来表示:

    那么对于这种结构,它算不算递归呢?事实上,函数FacShadow在这个例子中起到了一个“路由”的作用,它帮助函数Fac成功实现了调用自身的效果。从这种意义上来说,它应该是一个递归。但从另一个方面来讲,Fac和FacShadow是两个平等的函数,二者共同协作(通过特定的顺序互相调用)完成了阶乘的计算工作,这样理解来,递归的概念就有所牵强。无论如何,是不是递归的概念已经意义不大,真正的理解了代码的机制就可以了。

    代理形成的递归

    代理的出现使得对函数的操作如同对普通变量的操作一样方便。因此,“递归”又有了第三种实现形式:

    public static Func<int, int> FacFunc = null;
    public static int FacMethod(int n)
    {
        if (n <= 1) return 1;
        return FacFunc(n - 1) * n;
    }
    FacFunc = FacMethod;
    


    这与第二种“代理”相比有何不同呢?可以看出,在这种情况下,充当“路由”功能的是一个代理,而不是一个函数。又由于代理是函数的一个“签名”,其本身并没有函数的本体(而第二种中,FacShadow却是一个完整的函数),其“路由”的方式更直接,或者说原函数实现调用自身的方式比起第二种方式来说更直接一些。下图是FacFunc的执行过程图示:

    其中表示由代理解析成具体函数的一个过程。那么这种情况算不算递归呢?个人之见:这完全称的上递归。

    执行效率的比较

    了解了三种形式的“递归”之后,来比较一下它们的执行效率情况。根据它们的特点预先估计一下:第一种最直接,所以执行最快;第二种执行路径最多,执行最慢。所以它们执行效率的关系为:
    “传统递归” > “代理路由型递归” > “函数路由型递归”。
    实际的运行情况还需代码来检测,使用如下代码:

    using System;
    using System.Diagnostics;
    
    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                // initialize delegate routed recursion
                FacFunc = FacMethod;
    
                // create a lambda expression generated delegate routed recursion
                Func<int, int> funcLambda = null;
                funcLambda = n => n <= 1 ? 1 : n * funcLambda(n - 1);
    
                const int facIterations = 99999;
                const int v = 10;
    
                double typicalRecursion = 0;
                double methodRecursion = 0;
                double delegateRecursion = 0;
                double lambdaRecursion = 0;
                const int iterations = 500;
    
                for (int iteration = 0; iteration < iterations; ++iteration)
                {
                    // measure typical recursion
                    System.Diagnostics.Stopwatch watch = Stopwatch.StartNew();
                    for (int i = 0; i < facIterations; i++)
                    {
                        FacRecursive(v);
                    }
                    watch.Stop();
                    typicalRecursion += watch.ElapsedMilliseconds;
    
                    // measure delegate routed recursion
                    watch = Stopwatch.StartNew();
                    for (int i = 0; i < facIterations; i++)
                    {
                        FacFunc(v);
                    }
                    watch.Stop();
                    delegateRecursion += watch.ElapsedMilliseconds;
    
                    // measure lambda expression generated delegate routed recursion
                    watch = Stopwatch.StartNew();
                    for (int i = 0; i < facIterations; i++)
                    {
                        funcLambda(v);
                    }
                    watch.Stop();
                    lambdaRecursion += watch.ElapsedMilliseconds;
    
                    // measure method routed recursion
                    watch = Stopwatch.StartNew();
                    for (int i = 0; i < facIterations; i++)
                    {
                        Fac(v);
                    }
                    watch.Stop();
                    methodRecursion += watch.ElapsedMilliseconds;
                }
    
                Console.WriteLine("typical recursion: " + typicalRecursion / iterations);
                Console.WriteLine("Delegate routed recursion: " + delegateRecursion / iterations);
                Console.WriteLine("Lambda delegate routed recursion: " + lambdaRecursion / iterations);
                Console.WriteLine("Method routed recursion: " + methodRecursion / iterations);
                Console.ReadKey();
            }
    
            // typical recursion
            public static int FacRecursive(int n)
            {
                if (n <= 1) return 1;
                return FacRecursive(n - 1) * n;
            }
    
            // method routed recursion
            public static int Fac(int n)
            {
                if (n <= 1) return 1;
                return FacShadow(n - 1) * n;
            }
    
            public static int FacShadow(int n)
            {
                return Fac(n);
            }
    
            // delegate routed recursion
            public static Func<int, int> FacFunc = null;
            public static int FacMethod(int n)
            {
                if (n <= 1) return 1;
                return FacFunc(n - 1) * n;
            }
        }
    }
    代码比较长,做一下解释。测量一张纸的厚度很难,且不准确,可以通过测量一累纸的厚度然后除以张数来计算单张纸的厚度。同理,单个函数的执行时间很难准确测量,所以可以循环累加测量,然后取平均值,这就是代码中iterations = 500这个变量的用途。另一个要点,当时间很短时(比如小于1ms),其测量值不具代表意义,这就是代码中facIterations = 99999这个变量的用途,用来多次执行函数,我们把这个多次执行的时间作为参考来对比,当然不是直接对比,而是做iterations次再循环后取平均。看看结果如何:

    在Debug模式下输出:

    其结果跟前面分析的一致。接下来看看Release下的输出如何:

    这跟Debug模式下有很大的区别,可以看出在Release下编译器对程序做了优化,其中Method reouted recursion优化的最佳,其达到的效果几乎跟传统递归一样。其次是Lambda delegate routed recursion,优化之后其效率居然比手动写的Delegate routed recursion还高,而两者的结构却是一样的。

  • 相关阅读:
    Linux基础命令---mv
    Linux服务器---基础设置
    Linux基础命令---find
    Linux服务器配置---安装centos
    Linux基础命令---ls
    Linux基础命令---rmdir
    Linux基础命令---chown
    Linux基础命令---chmod
    Linux基础命令---chgrp
    Linux基础命令---ln
  • 原文地址:https://www.cnblogs.com/lsp/p/1656373.html
Copyright © 2020-2023  润新知