Python中尾递归版阶乘函数为何远慢于迭代及非尾递归版本?
为啥Python里尾递归阶乘反而比迭代、非尾递归慢?
兄弟,你观察到的这个现象太正常了——Python的官方解释器CPython根本就不支持尾递归优化(TRO),这就是核心原因!结合你贴的性能数据,咱们掰扯清楚:
1. 尾递归在Python里根本没被优化,反而多了额外开销
尾递归本来是给那些支持TRO的语言(比如Scheme、Haskell)设计的,在那些语言里,尾递归调用会被直接转换成循环,不用反复创建栈帧。但CPython的开发者明确拒绝做这个优化——主要是为了保留完整的栈回溯信息,方便咱们调试的时候看调用栈。
所以你写的两个尾递归版本fact_tail_recur和fact_tail_recur_2,每一次递归调用都得新建一个栈帧,和非尾递归fact_non_tail_recur一样有栈开销,但为啥尾递归更慢?看你给的性能数据:
- 非尾递归里
product = n * fact_non_tail_recur(n - 1)这行才花了16481μs,return product是12974μs - 但尾递归
fact_tail_recur里product *= n直接干到55390μs,是非尾递归那行的3倍多!
2. 尾递归的参数传递+局部变量操作,额外开销拉满
仔细看你的尾递归实现:
- 每次递归都得把更新后的
product当参数传进去,CPython在函数调用的时候要处理参数的打包、解包,这比非尾递归在栈帧返回后再做乘法的开销大太多了。 - 非尾递归的乘法是在递归返回阶段做的,这时候栈帧都在准备销毁了;而尾递归的乘法是在进下一次递归前做,还得把结果当参数传出去,这两步加起来开销直接上去了。
反观迭代版本fact_iter,for循环是CPython用C实现的高度优化操作,完全没有Python层面的函数调用开销,所以耗时最低(0.040521s),这完全符合预期。
3. 你的两个尾递归版本的细微差异
fact_tail_recur_2比fact_tail_recur稍微快一点,但还是远慢于另外两个:
- 它的
product *= i耗时49390μs,比前者的55390μs低一点,这只是因为一个是递减n、一个是递增i,数值操作的微小差异,本质还是参数传递的开销在作祟。
最后总结:Python里别用尾递归搞性能
要是想在Python里写高效的阶乘,迭代版本永远是最优解,如果觉得非尾递归逻辑更清晰也可以用,但尾递归真的没必要——它既拿不到TRO的性能好处,反而因为参数传递和额外操作多了不少开销。
内容的提问来源于stack exchange,提问作者DavidW1010
相关产品推荐
相关产品推荐

