Gear.Turbo

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还高,而两者的结构却是一样的。

posted on 2010-01-26 00:52  lsp  阅读(440)  评论(0编辑  收藏  举报

导航