浅谈尾递归的优化方式
r ds:[703068h] (地址 703068h的值即为00ad00d0) 00ad00f9 pop esi 00ad00fa pop ebp 00ad00fb ret 上面的汇编代码非常简单,从中可以看出,每次递归调用都使用了最简单的call指令,没有经过任何 有效的优化或调整。因此在不断地递归调用之后,终究会出现堆栈溢出。这就是普通递归的缺陷。而对于 尾递归来说,MSIL提供了额外的tail指令表示“尾调用”1,它只需简单补充在IL指令call, callvirt, calli之前便可。因此我们使用ildasm.exe将IL代码dump出来,并在call之前加上tail指令:
使用ilasm.exe重新编译之后运行,再重新察看FactorialTailRecursion的汇编代码:
在这里我实在无法完整讲述上述汇编代码的含义,不过从中可以看出它的确对于尾递归进行了特别的 处理,而并非使用简单的call指令进行调用。对此互联网上的资源也不多,老赵只找到了Shri Borde的一 篇文章,其中简单描述了Whidbey V2(真早)中CLR对于这方面的处理,以及一些相关的考虑,从中似乎 能够看出一些苗头来。 让我们再回想之前的问题:Continuation无法通过简单优化为循环来解决递归问题。但是通过观察可 以看出,Continuation.Invoke方法中的cont委托调用是最后一条命令,这说明它是一个“尾调用”—— 虽然不是“尾递归”,不过这已经满足tail指令的要求了:只需和所在方法返回值相同(或兼容)即可。 因此,对于Continuation来说,我们也需要进行尾递归的优化。您可以进行尝试,现在无论递归多“深” ,都不会使堆栈溢出了。 注1:与tail类似,IL指令jmp也能够用于方法调用。这两条指令都不会在调用栈上堆积数据。tail与 jmp的不同之处在于,前者只需要返回值与所在方法相同或兼容即可,而后者需要签名完全相同。您可以 想象得到,对于“直接递归”来说,可以使用jmp进行调用,而普通的“尾调用”,则只能使用tail了 |
凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢! |