摘要:尾遞歸優(yōu)化一般遞歸與尾遞歸一般遞歸執(zhí)行可以看到一般遞歸每一級(jí)遞歸都產(chǎn)生了新的局部變量必須創(chuàng)建新的調(diào)用棧隨著遞歸深度的增加創(chuàng)建的棧越來(lái)越多造成爆棧尾遞歸尾遞歸基于函數(shù)的尾調(diào)用每一級(jí)調(diào)用直接返回遞歸函數(shù)更新調(diào)用棧沒(méi)有新局部變量的產(chǎn)生類(lèi)似迭代的
Python尾遞歸優(yōu)化 一般遞歸與尾遞歸 一般遞歸:
def normal_recursion(n): if n == 1: return 1 else: return n + normal_recursion(n-1)
執(zhí)行:
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
可以看到, 一般遞歸, 每一級(jí)遞歸都產(chǎn)生了新的局部變量, 必須創(chuàng)建新的調(diào)用棧, 隨著遞歸深度的增加, 創(chuàng)建的棧越來(lái)越多, 造成爆棧?
尾遞歸尾遞歸基于函數(shù)的尾調(diào)用, 每一級(jí)調(diào)用直接返回遞歸函數(shù)更新調(diào)用棧, 沒(méi)有新局部變量的產(chǎn)生, 類(lèi)似迭代的實(shí)現(xiàn):
def tail_recursion(n, total=0): if n == 0: return total else: return tail_recursion(n-1, total+n)
執(zhí)行:
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
可以看到, 尾遞歸每一級(jí)遞歸函數(shù)的調(diào)用變成"線性"的形式. 這時(shí), 我們可以思考, 雖然尾遞歸調(diào)用也會(huì)創(chuàng)建新的棧, 但是我們可以優(yōu)化使得尾遞歸的每一級(jí)調(diào)用共用一個(gè)棧!, 如此便可解決爆棧和遞歸深度限制的問(wèn)題!
C中尾遞歸的優(yōu)化gcc使用-O2參數(shù)開(kāi)啟尾遞歸優(yōu)化:
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開(kāi)啟尾遞歸優(yōu)化
對(duì)比反匯編代碼如下(AT&T語(yǔ)法, 左圖為優(yōu)化后)
可以看到, 開(kāi)啟尾遞歸優(yōu)化前, 使用call調(diào)用函數(shù), 創(chuàng)建了新的調(diào)用棧(LBB0_3); 而開(kāi)啟尾遞歸優(yōu)化后, 就沒(méi)有新的調(diào)用棧生成了, 而是直接pop bp指向的_tail_recursion函數(shù)的地址(pushq %rbp)然后返回, 仍舊用的是同一個(gè)調(diào)用棧!
Python開(kāi)啟尾遞歸優(yōu)化cpython本身不支持尾遞歸優(yōu)化, 但是一個(gè)牛人想出的解決辦法:實(shí)現(xiàn)一個(gè) 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()函數(shù):
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深度調(diào)用的棧幀對(duì)象.
import sys def get_cur_info(): print sys._getframe().f_code.co_filename # 當(dāng)前文件名 print sys._getframe().f_code.co_name # 當(dāng)前函數(shù)名 print sys._getframe().f_lineno # 當(dāng)前行號(hào) print sys._getframe().f_back # 調(diào)用者的幀
更多關(guān)于sys._getframe的使用請(qǐng)看Frame Hacks
說(shuō)一下tail_call_optimized實(shí)現(xiàn)尾遞歸優(yōu)化的原理: 當(dāng)遞歸函數(shù)被該裝飾器修飾后, 遞歸調(diào)用在裝飾器while循環(huán)內(nèi)部進(jìn)行, 每當(dāng)產(chǎn)生新的遞歸調(diào)用棧幀時(shí): f.f_back.f_back.f_code == f.f_code:, 就捕獲當(dāng)前尾調(diào)用函數(shù)的參數(shù), 并拋出異常, 從而銷(xiāo)毀遞歸棧并使用捕獲的參數(shù)手動(dòng)調(diào)用遞歸函數(shù). 所以遞歸的過(guò)程中始終只存在一個(gè)棧幀對(duì)象, 達(dá)到優(yōu)化的目的.
為了更清晰的展示開(kāi)啟尾遞歸優(yōu)化前、后調(diào)用棧的變化和tail_call_optimized裝飾器拋異常退出遞歸調(diào)用棧的作用, 我這里利用pudb調(diào)試工具做了動(dòng)圖:
開(kāi)啟尾遞歸優(yōu)化前的調(diào)用棧
開(kāi)啟尾遞歸優(yōu)化后(tail_call_optimized裝飾器)的調(diào)用棧
通過(guò)pudb右邊欄的stack, 可以很清晰的看到調(diào)用棧的變化.
因?yàn)閷?shí)現(xiàn)了尾遞歸優(yōu)化, 所以factorial(10000)都不害怕遞歸深度限制報(bào)錯(cuò)啦!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/44328.html
摘要:根據(jù)該規(guī)則,返回第個(gè)斐波那契數(shù)。尾遞歸函數(shù)調(diào)用自身,稱為遞歸。一個(gè)前端眼中的斐波那契數(shù)列解斐波那契數(shù)列的實(shí)用解法調(diào)用棧尾遞歸和手動(dòng)優(yōu)化尾調(diào)用優(yōu)化譯我從用寫(xiě)斐波那契生成器中學(xué)到的令人驚訝的件事 斐波那契數(shù)列是以下一系列數(shù)字: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, ... 在種子數(shù)字 0 和 1 ...
摘要:遞歸版本尾遞歸很多遞歸沒(méi)辦法自然的寫(xiě)成尾遞歸,本質(zhì)原因是無(wú)法在多次遞歸過(guò)程中維護(hù)共有的變量,這也是循環(huán)的優(yōu)勢(shì)所在。這是因?yàn)殡m然用的,但并沒(méi)有開(kāi)啟尾遞歸優(yōu)化。 TL;DR 為一個(gè)已排序的鏈表去重,考慮到很長(zhǎng)的鏈表,需要尾調(diào)用優(yōu)化。系列目錄見(jiàn) 前言和目錄 。 需求 實(shí)現(xiàn)一個(gè) removeDuplicates() 函數(shù),給定一個(gè)升序排列過(guò)的鏈表,去除鏈表中重復(fù)的元素,并返回修改后的鏈表。理想...
摘要:默認(rèn)參數(shù)設(shè)置默認(rèn)參數(shù)時(shí),有幾點(diǎn)要注意一是必選參數(shù)在前,默認(rèn)參數(shù)在后,否則的解釋器會(huì)報(bào)錯(cuò)二是如何設(shè)置默認(rèn)參數(shù)。注意此處,獲得的其實(shí)是的拷貝,函數(shù)內(nèi)對(duì)的改變不會(huì)影響到。使用遞歸函數(shù)需要注意防止棧溢出。 總是在最前面的叨逼叨 最近總是在想成長(zhǎng)這兩個(gè)很常常被提起的事情,這對(duì)于一個(gè)已經(jīng)25歲的半中年而言,已經(jīng)是一個(gè)不太能高頻提起的詞。但是,最近一些事情吧,總讓我覺(jué)得我的生長(zhǎng)期似乎比正常人來(lái)的晚了...
摘要:針對(duì)尾遞歸優(yōu)化的語(yǔ)言可以通過(guò)尾遞歸防止棧溢出。尾遞歸事實(shí)上和循環(huán)是等價(jià)的,沒(méi)有循環(huán)語(yǔ)句的編程語(yǔ)言只能通過(guò)尾遞歸實(shí)現(xiàn)循環(huán)。標(biāo)準(zhǔn)的解釋器沒(méi)有針對(duì)尾遞歸做優(yōu)化,任何遞歸函數(shù)都存在棧溢出的問(wèn)題。 python 頭部: #!/usr/bin/env python # -*- coding: utf-8 -*- 函數(shù)的參數(shù) Python的函數(shù)具有非常靈活的參數(shù)形態(tài),既可以實(shí)現(xiàn)簡(jiǎn)單的調(diào)用,又可以傳入...
摘要:在定義函數(shù)時(shí)給定的名稱稱作形參,在調(diào)用函數(shù)時(shí)你所提供給函數(shù)的值稱作實(shí)參。調(diào)用函數(shù)要調(diào)用一個(gè)函數(shù),需要知道函數(shù)的名稱和參數(shù)。默認(rèn)參數(shù)值可以有效幫助解決這一情況。是默認(rèn)參數(shù)定義默認(rèn)參數(shù)要牢記一點(diǎn)默認(rèn)參數(shù)必須指向不變對(duì)象。 關(guān)于數(shù)據(jù)科學(xué)在做什么,我們已經(jīng)在前兩篇文章中進(jìn)行了總結(jié),即專(zhuān)題概述和描述性統(tǒng)計(jì)分析。要進(jìn)行數(shù)據(jù)科學(xué)的探索,需要一個(gè)好工具,就是Python。從本篇開(kāi)始,將總結(jié)學(xué)習(xí)Pyth...
閱讀 2264·2023-04-26 01:50
閱讀 720·2021-09-22 15:20
閱讀 2599·2019-08-30 15:53
閱讀 1602·2019-08-30 12:49
閱讀 1718·2019-08-26 14:05
閱讀 2716·2019-08-26 11:42
閱讀 2310·2019-08-26 10:40
閱讀 2607·2019-08-26 10:38