不要让递归函数fuck大家的cpu

递归算法是大学计算机课程里面经常会讲到的编程方法,因为采用这种方法写出来的代码清晰易懂。

但是,在大多数编程规范里面,会**严令禁止**使用递归函数,原因下面来详细说明。

首先,由于逻辑错误,由直接或间接递归,造成递归调用无法结束(死递归),最后肯定会收到
一个"stack overflow"的宕机信息。就暂且不论了。

下面要详细讨论的是,简单的递归代码是如何fuck计算机运行时系统的。

这里用计算Fibonacci(斐波纳契)数列举例。

由于在数学公式里,Fibonacci数列就是一个递归定义:
        Fibonacci(0)=0
        Fibonacci(1)=1
        Fibonacci(n)=Fibonacci(n-1)+Fibonacci(n-2)         (n>=2)
因而,最容易想到的就是用递归方法实现:
        func fibonacci(n uint64) uint64 {
            x := n
            if n > 1 {
                x = fibonacci(n-1) + fibonacci(n-2)
            }
            return x
        }
然而,这看似清晰简单的递归函数,对计算机运行时系统而言,却是毁灭性的灾难。

由于每次函数调用,编译器都会帮我们向运行栈push参数和返回地址,以便被调用函数可以
正确收到调用参数和返回。由于正常的函数调用层次一般不会太深,而且由于可能会有多线
程或协程(如:goroutine),为了节约资源,所以每一个执行单元的运行时栈一般不会设计
得无限大。

然而这看似简单的递归调用,却是用可能会无限大的参数N,让运行时系统实现调用层次为
2N(N-1 + N-2)的函数调用队列。这种情况,是任何设计灵活的运行时系统都无法承受
的计算模式。

由于go语言要实现goroutine,设计了可动态增长的运行时栈,但是在N=50的时候,以上
代码已经不能在可以忍受的时间内给出计算结果。

其实,所有看似只能用递归原语写出的代码,都是可以通过一种可以被称为stack的数据结构
替代,从而用for循环实现相同的逻辑。
以下是两种用for循环实现计算Fibonacci函数的方法:
        func fibonacci2(n uint64) uint64 {
            f0, f1 := uint64(0), uint64(1)
            for i := uint64(2); i <= n; i++ {
                f0, f1 = f1, f0+f1
            }
            return f1
        }
        func fibonacci3(n uint64) uint64 {
            var stack Stack
            for j := n - 1; j >= 2; j-- {
                stack.push(j)
            }
            f0, f1 := uint64(0), uint64(1)
            for _, ok := stack.pop(); ok; _, ok = stack.pop() {
                f0, f1 = f1, f0+f1
            }
            return f1
        }
这种通过普通for循环实现递归函数的方法,由于不需要增加函数调用层次,所以对N的大小
可以没有任何限制。
实测N=10亿,以上函数仍然可以在1ns内给出计算结果(当然已经被uint64截断)。
以下是N=45的时候,以上三个函数的Benchmark结果:
        go test -test.bench=".*"
        N = 45
        testing: warning: no tests to run
        Benchmark_Recursive-4                  1        10018573100 ns/op
        Benchmark_Loop-4                2000000000               0.00 ns/op
        Benchmark_Stack-4               2000000000               0.00 ns/op
        PASS
        ok      github.com/vipally/glab/lab2    10.154s
可以显见,递归函数对于计算机系统来说,该是怎样的灾难。
所以,有经验的技术经理会在项目组里严令禁止使用递归函数是明智的。
因此,不要贪图一时方便,让悄悄躲在某个角落的递归调用fuck大家的CPU。

我将以上测试代码放在这里:
https://github.com/vipally/glab/blob/master/lab2/lab2_test.go
欢迎指教。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值