<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
def normal_recursion(n): if n == 1: return 1 else: return n + normal_recursion(n-1)
執行:
normal_recursion(5) 5 + normal_recursion(4) 5 + 4 + normal_recursion(3) 5 + 4 + 3 + normal_recursion(2) 5 + 4 + 3 + 2 + normal_recursion(1) 5 + 4 + 3 + 3 5 + 4 + 6 5 + 10 15
可以看到, 一般遞迴, 每一級遞迴都產生了新的區域性變數, 必須建立新的呼叫棧, 隨著遞迴深度的增加, 建立的棧越來越多, 造成爆棧?
尾遞迴基於函數的尾呼叫, 每一級呼叫直接返回遞迴函數更新呼叫棧, 沒有新區域性變數的產生, 類似迭代的實現:
def tail_recursion(n, total=0): if n == 0: return total else: return tail_recursion(n-1, total+n)
執行:
tail_recursion(5, 0) tail_recursion(4, 5) tail_recursion(3, 9) tail_recursion(2, 12) tail_recursion(1, 14) tail_recursion(0, 15) 15
可以看到, 尾遞迴每一級遞迴函數的呼叫變成"線性"的形式. 這時, 我們可以思考, 雖然尾遞迴呼叫也會建立新的棧, 但是我們可以優化使得尾遞迴的每一級呼叫共用一個棧!, 如此便可解決爆棧和遞迴深度限制的問題!
gcc使用-O2
引數開啟尾遞迴優化:
int tail_recursion(int n, int total) { if (n == 0) { return total; } else { return tail_recursion(n-1, total+n); } } int main(void) { int total = 0, n = 4; tail_recursion(n, total); return 0; }
反組合
$ gcc -S tail_recursion.c -o normal_recursion.S $ gcc -S -O2 tail_recursion.c -o tail_recursion.S gcc開啟尾遞迴優化
對比反組合程式碼如下(AT&T語法, 左圖為優化後)
可以看到, 開啟尾遞迴優化前, 使用call呼叫函數, 建立了新的呼叫棧(LBB0_3); 而開啟尾遞迴優化後, 就沒有新的呼叫棧生成了, 而是直接pop bp指向的_tail_recursion函數的地址(pushq %rbp)然後返回, 仍舊用的是同一個呼叫棧!
cpython本身不支援尾遞迴優化, 但是一個牛人想出的解決辦法:
實現一個 tail_call_optimized 裝飾器
#!/usr/bin/env python2.4 # This program shows off a python decorator( # which implements tail call optimization. It # does this by throwing an exception if it is # it's own grandparent, and catching such # exceptions to recall the stack. import sys class TailRecurseException: def __init__(self, args, kwargs): self.args = args self.kwargs = kwargs def tail_call_optimized(g): """ This function decorates a function with tail call optimization. It does this by throwing an exception if it is it's own grandparent, and catching such exceptions to fake the tail call optimization. This function fails if the decorated function recurses in a non-tail context. """ def func(*args, **kwargs): f = sys._getframe() if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code: # 丟擲異常 raise TailRecurseException(args, kwargs) else: while 1: try: return g(*args, **kwargs) except TailRecurseException, e: args = e.args kwargs = e.kwargs func.__doc__ = g.__doc__ return func @tail_call_optimized def factorial(n, acc=1): "calculate a factorial" if n == 0: return acc return factorial(n-1, n*acc) print factorial(10000)
這裡解釋一下sys._getframe()函數:
sys._getframe([depth]):
Return a frame object from the call stack.
If optional integer depth is given, return the frame object that many calls below the top of the stack.
If that is deeper than the call stack, ValueEfror is raised. The default for depth is zero,
returning the frame at the top of the call stack.
即返回depth深度呼叫的棧幀物件.
import sys def get_cur_info(): print sys._getframe().f_code.co_filename # 當前檔名 print sys._getframe().f_code.co_name # 當前函數名 print sys._getframe().f_lineno # 當前行號 print sys._getframe().f_back # 呼叫者的幀
更多關於sys._getframe的使用請看https://www.jb51.net/article/181387.htm
說一下tail_call_optimized實現尾遞迴優化的原理:
當遞迴函數被該裝飾器修飾後, 遞迴呼叫在裝飾器while迴圈內部進行, 每當產生新的遞迴呼叫棧幀時: f.f_back.f_back.f_code == f.f_code:, 就捕獲當前尾呼叫函數的引數, 並丟擲異常, 從而銷燬遞迴棧並使用捕獲的引數手動呼叫遞迴函數. 所以遞迴的過程中始終只存在一個棧幀物件, 達到優化的目的.
為了更清晰的展示開啟尾遞迴優化前、後呼叫棧的變化和tail_call_optimized裝飾器拋異常退出遞迴呼叫棧的作用, 我這裡利用pudb偵錯工具做了動圖:
開啟尾遞迴優化前的呼叫棧
開啟尾遞迴優化後(tail_call_optimized裝飾器)的呼叫棧
通過pudb右邊欄的stack, 可以很清晰的看到呼叫棧的變化.
因為實現了尾遞迴優化, 所以factorial(10000)都不害怕遞迴深度限制報錯啦!
以上就是Python開啟尾遞迴優化實現範例的詳細內容,更多關於Python尾遞迴優化!的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45