摘要:免責(zé)聲明不要過早地進(jìn)行優(yōu)化有關(guān)過早優(yōu)化的詳細(xì)分析請(qǐng)查閱本文。如果在龐大的應(yīng)用中運(yùn)行該分析工具,會(huì)得到一張巨大的圖片。免責(zé)聲明請(qǐng)確保該方法只用于函數(shù)如果將記憶用于帶有副作用譬如的函數(shù),緩存可能無法達(dá)到預(yù)期的效果。
【編者按】本文作者為來自 HumanGeo 的工程師 Davis,主要介紹了用于 Python 應(yīng)用性能分析的幾個(gè)工具。由國內(nèi) ITOM 管理平臺(tái) OneAPM 編譯呈現(xiàn)。
在 HumanGeo,我們廣泛使用 Python 進(jìn)行編程,并且樂趣無窮。用 Python 寫的程序不僅整潔美觀,而且運(yùn)行速度快得驚人。不論是私底下還是工作中,Python 都是筆者最愛的語言。然而,即便是 Python 這樣美妙的語言,卻也可能出現(xiàn)運(yùn)行緩慢的情況。幸運(yùn)的是,有許多不錯(cuò)的工具,可以幫助我們分析 Python 代碼,從而保證其運(yùn)行效率。
當(dāng)筆者剛開始在 HumanGeo 工作時(shí),就曾遇到過一個(gè)運(yùn)行一次耗時(shí)數(shù)小時(shí)的程序,而筆者的任務(wù),就是找出其性能瓶頸,再盡可能地提高其運(yùn)行效率。當(dāng)時(shí),筆者使用了許多工具,包括 cProfile][5], [PyCallGraph][6,甚至 PyPy(一個(gè)運(yùn)行快速的 Python 解釋器),以確定最佳的程序優(yōu)化方案。在本文中,筆者將介紹上述工具(為了保持生產(chǎn)環(huán)境中的解釋器一致性,本文將不會(huì)介紹 PyPy 工具)的使用方法。甚至即便是最老練的開發(fā)者,也可以借助這些工具進(jìn)一步優(yōu)化他們的代碼。
免責(zé)聲明:不要過早地進(jìn)行優(yōu)化!有關(guān)過早優(yōu)化的詳細(xì)分析請(qǐng)查閱本文。
工具閑話少敘,下面開始介紹分析 Python 代碼的幾種便捷工具。
cProfileCPython distribution 自帶兩種分析工具:profile 與 cProfile。兩者使用同樣的 API,按理說運(yùn)行效果應(yīng)該差不多。然而,前者的運(yùn)行時(shí)開銷更大,因此,本文將主要介紹 cProfile。
借助 cProfile,可以輕松實(shí)現(xiàn)對(duì)代碼的深入分析,并且了解代碼的哪些部分亟待提升。查看下面的緩慢代碼實(shí)例:
--> % cat slow.py import time def main(): sum = 0 for i in range(10): sum += expensive(i // 2) return sum def expensive(t): time.sleep(t) return t if __name__ == "__main__": print(main())
在上面的代碼中,筆者通過調(diào)用 time.sleep 方法,模擬一個(gè)運(yùn)行時(shí)間很長(zhǎng)的程序,并假定運(yùn)行結(jié)果很重要。接下來,對(duì)這段代碼進(jìn)行分析,結(jié)果如下:
--> % python -m cProfile slow.py 20 34 function calls in 20.030 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 __future__.py:48() 1 0.000 0.000 0.000 0.000 __future__.py:74(_Feature) 7 0.000 0.000 0.000 0.000 __future__.py:75(__init__) 10 0.000 0.000 20.027 2.003 slow.py:11(expensive) 1 0.002 0.002 20.030 20.030 slow.py:2( ) 1 0.000 0.000 20.027 20.027 slow.py:5(main) 1 0.000 0.000 0.000 0.000 {method "disable" of "_lsprof.Profiler" objects} 1 0.000 0.000 0.000 0.000 {print} 1 0.000 0.000 0.000 0.000 {range} 10 20.027 2.003 20.027 2.003 {time.sleep}
我們發(fā)現(xiàn),分析結(jié)果相當(dāng)瑣碎。其實(shí),可以用更有益的方式組織分析結(jié)果。在上例中,調(diào)用列表是按照字母順序排列的,這對(duì)我們并無價(jià)值。筆者更愿意看到按照調(diào)用次數(shù)或累計(jì)運(yùn)行時(shí)間排列的調(diào)用情況。幸運(yùn)的是,通過 -s 參數(shù)就能實(shí)現(xiàn)這一點(diǎn)。我們馬上就能看到存在問題的代碼段了!
--> % python -m cProfile -s calls slow.py 20 34 function calls in 20.028 seconds Ordered by: call count ncalls tottime percall cumtime percall filename:lineno(function) 10 0.000 0.000 20.025 2.003 slow.py:11(expensive) 10 20.025 2.003 20.025 2.003 {time.sleep} 7 0.000 0.000 0.000 0.000 __future__.py:75(__init__) 1 0.000 0.000 20.026 20.026 slow.py:5(main) 1 0.000 0.000 0.000 0.000 __future__.py:74(_Feature) 1 0.000 0.000 0.000 0.000 {print} 1 0.000 0.000 0.000 0.000 __future__.py:48() 1 0.003 0.003 20.028 20.028 slow.py:2( ) 1 0.000 0.000 0.000 0.000 {method "disable" of "_lsprof.Profiler" objects} 1 0.000 0.000 0.000 0.000 {range}
果然!我們發(fā)現(xiàn),存在問題的代碼就在 expensive 函數(shù)當(dāng)中。該函數(shù)在執(zhí)行結(jié)束之前調(diào)用了多次 time.sleep 方法,因此導(dǎo)致了程序的速度下降。
-s參數(shù)的有效取值列表可以在此 Python 文檔中找到。如果你想將分析結(jié)果保存到一個(gè)文件中,記得使用輸出選項(xiàng) -o。
基本功能介紹完畢之后,讓我們來看看使用分析工具查找問題代碼的其他方法。
PyCallGraphPyCallGraph 可以看做是 cProfile 的可視化擴(kuò)展工具。借助該工具,我們可以通過出色的 Graphviz 圖片了解代碼執(zhí)行的路徑。PyCallGraph 并未包含在標(biāo)準(zhǔn)的 Python 安裝包內(nèi),因此,需要通過如下語句,進(jìn)行簡(jiǎn)單的安裝:
-> % pip install pycallgraph
通過下面的指令,就能運(yùn)行圖形化應(yīng)用:
-> % pycallgraph graphviz -- python slow.py
運(yùn)行完畢之后,在運(yùn)行腳本的目錄下會(huì)出現(xiàn)一張 pycallgraph.png 圖片文件。同時(shí),還應(yīng)該得到相似的分析結(jié)果(如果你之前已經(jīng)用 cProfile 分析過了)。結(jié)果中的數(shù)據(jù)應(yīng)該與 cProfile 提供的結(jié)果一致。不過,PyCallGraph 的優(yōu)點(diǎn)在于,它能展示被調(diào)用函數(shù)相互間的關(guān)系。
讓我們來看看圖片到底長(zhǎng)什么樣:
這多方便?。D片顯示了程序的運(yùn)行路徑,告訴我們程序經(jīng)歷過的每個(gè)函數(shù)、模塊以及文件,還帶有運(yùn)行時(shí)間與調(diào)用次數(shù)等信息。如果在龐大的應(yīng)用中運(yùn)行該分析工具,會(huì)得到一張巨大的圖片。但是,根據(jù)顏色的差別,我們?nèi)阅茌p易找到存在問題的代碼塊。下面是 PyCallGraph 文檔中提供的一張圖片,展示了一段復(fù)雜的正則表達(dá)式調(diào)用中代碼的運(yùn)行路徑:
點(diǎn)此獲取此圖分析的源碼
這些信息有什么用?一旦我們確定了導(dǎo)致問題代碼的根源,就可以選擇合適的解決方案優(yōu)化代碼,為其提速。下面,讓我們根據(jù)特定的情況,探討一些緩慢代碼可行的解決方案。
I/O如果你發(fā)現(xiàn)自己的代碼嚴(yán)重依賴于輸入/輸出,譬如,需要發(fā)送很多 Web 請(qǐng)求,那么,Python 的標(biāo)準(zhǔn)線程模塊或許就能幫你解決該問題。由于 CPython 的全局鎖機(jī)制(Global Interpreter Lock,GIL)不允許為代碼中心任務(wù)同時(shí)使用多個(gè)核,非 I/O 相關(guān)的線程并不適合用 Python 實(shí)現(xiàn)。
正則表達(dá)式人們都說,一旦你決定用正則表達(dá)式解決某個(gè)問題,你就有兩個(gè)問題要解決了。正則表達(dá)式真的很難用對(duì),而且難以維護(hù)。關(guān)于這一點(diǎn),筆者可以寫一篇長(zhǎng)篇大論進(jìn)行闡述。(但是,我不會(huì)寫的:)。正則表達(dá)式真的不簡(jiǎn)單,我相信有很多博文已經(jīng)做了詳盡的闡述。)不過,在此,筆者將介紹幾個(gè)有用的技巧:
避免使用 .*,貪婪的匹配所有運(yùn)算符運(yùn)行起來非常慢,盡可能使用字符類才是更好的選擇。
避免使用正則表達(dá)式!其實(shí),許多正則表達(dá)式都可以用簡(jiǎn)單的字符串方法替代,比如 str.startswith 與 str.endswith
方法。閱讀 str 文檔可以找到更多有用的信息。
多使用 re.VERBOSE!Python 的正則表達(dá)式引擎非常強(qiáng)大,超級(jí)有用,一定要好好利用!
以上是有關(guān)正則表達(dá)式筆者想說的全部?jī)?nèi)容。如果你想要更多信息,相信網(wǎng)絡(luò)上還有很多好的文章。
Python 代碼以筆者之前剖析過的代碼為例,我們的 Python 函數(shù)會(huì)運(yùn)行成千上萬次以找出英文詞的詞根。該函數(shù)最迷人的地方在于,其進(jìn)行的操作很容易緩存。保存函數(shù)的運(yùn)行結(jié)果之后,代碼的運(yùn)行速度提升了整整十倍。而在 Python 中創(chuàng)建緩存是輕而易舉的事情:
from functools import wraps def memoize(f): cache = {} @wraps(f) def inner(arg): if arg not in cache: cache[arg] = f(arg) return cache[arg] return inner
該技術(shù)名為記憶(memoization),在具體實(shí)現(xiàn)時(shí)會(huì)執(zhí)行為裝飾器,可輕易應(yīng)用在 Python 函數(shù)中,如下所示:
import time @memoize def slow(you): time.sleep(3) print("Hello after 3 seconds, {}!".format(you)) return 3
現(xiàn)在,如果我們多次運(yùn)行該函數(shù),運(yùn)行結(jié)果就會(huì)立即出現(xiàn):
>>> slow("Davis") Hello after 3 seconds, Davis! 3 >>> slow("Davis") 3 >>> slow("Visitor") Hello after 3 seconds, Visitor! 3 >>> slow("Visitor") 3
對(duì)于該項(xiàng)目來說,這是極大的速度提升。而且代碼運(yùn)行起來也沒有出現(xiàn)故障。
免責(zé)聲明:請(qǐng)確保該方法只用于 pure 函數(shù)!如果將記憶(memoization)用于帶有副作用(譬如:I/O)的函數(shù),緩存可能無法達(dá)到預(yù)期的效果。
其他情況如果你的代碼無法使用記憶(memoization)技巧,你的算法也不像 O(n!) 這樣瘋狂,或者代碼的剖析結(jié)果也沒有引人注意的地方,這可能說明你的代碼并不存在顯著的問題。這時(shí)候,你可以嘗試一下別的運(yùn)行環(huán)境或語言。PyPy 就是一個(gè)好的選擇,你可能還要將算法用C語言擴(kuò)展方法重寫一下。幸運(yùn)的是,筆者之前的項(xiàng)目并未走到這一步,但是這仍是很好的排錯(cuò)方案。
結(jié)論剖析代碼可以幫助你理解項(xiàng)目的執(zhí)行流程、找出潛在的問題代碼,以及作為開發(fā)者該如何提升程序運(yùn)行速度。Python 剖析工具不但功能強(qiáng)大,簡(jiǎn)單易用,而且足夠深入以快速找出問題根源。雖然 Python 并不是以快速著稱的語言,但這并不意味著你的代碼應(yīng)該拖拖拉拉。管理好自己的算法,適時(shí)進(jìn)行剖析,但絕不要過早優(yōu)化!
本文轉(zhuǎn)自 OneAPM 官方博客
原文地址:http://blog.thehumangeo.com/2015/07/28/profiling-in-python/
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/37890.html
摘要:蠎周刊年度最贊親俺們又來回顧又一個(gè)偉大的年份兒包去年最受歡迎的文章和項(xiàng)目如果你錯(cuò)過了幾期就這一期不會(huì)丟失最好的嗯哼還為你和你的準(zhǔn)備了一批紀(jì)念裇從這兒獲取任何時(shí)候如果想分享好物給大家在這兒提交喜歡我們收集的任何意見建議通過來吧原文 Title: 蠎周刊 2015 年度最贊Date: 2016-01-09 Tags: Weekly,Pycoder,Zh Slug: issue-198-to...
摘要:云計(jì)算剖析本文將從云架構(gòu)的核心抽象開始討論從基礎(chǔ)架構(gòu)即服務(wù),然后是構(gòu)建塊,最后是高度集成的解決方案。核心開源技術(shù)領(lǐng)域經(jīng)歷著以虛擬化管理和大規(guī)模云軟件包集成的虛擬基礎(chǔ)架構(gòu)為中心的開發(fā)浪潮。 對(duì)分布式系統(tǒng)也就是 Internet 使用云作為一種抽象是十分普遍的,但在過去的幾年中,這個(gè)抽象已經(jīng)擴(kuò)展,并入了高度虛擬化的可伸縮基礎(chǔ)架構(gòu),這些基礎(chǔ)架構(gòu)可以很容易地被作為一個(gè)(本地的或遠(yuǎn)程的,或者本地和遠(yuǎn)...
摘要:本文將分享軟件基本用法及文件進(jìn)程注冊(cè)表查看,這是一款微軟推薦的系統(tǒng)監(jiān)視工具,功能非常強(qiáng)大可用來檢測(cè)惡意軟件。可以幫助使用者對(duì)系統(tǒng)中的任何文件注冊(cè)表操作進(jìn)行監(jiān)視和記錄,通過注冊(cè)表和文件讀寫的變化,有效幫助診斷系統(tǒng)故障或發(fā)現(xiàn)惡意軟件病毒及木馬。 ...
摘要:以下內(nèi)容僅針對(duì)版書籍,等新版上市后,薦書欄目會(huì)對(duì)兩版的差異跟進(jìn)介紹。當(dāng)然,后續(xù)其它薦書的書目,也很有可能會(huì)送福利,一樣不容錯(cuò)過。 showImg(https://segmentfault.com/img/bVbjIxq?w=6000&h=4000); 大家好,新一期的薦書欄目如期跟大家見面了。 先來看看今天的主角是誰:《Python源碼剖析——深度探索動(dòng)態(tài)語言核心技術(shù)》,2008年出版...
閱讀 3031·2021-11-22 12:06
閱讀 612·2021-09-03 10:29
閱讀 6575·2021-09-02 09:52
閱讀 2027·2019-08-30 15:52
閱讀 3423·2019-08-29 16:39
閱讀 1198·2019-08-29 15:35
閱讀 2072·2019-08-29 15:17
閱讀 1433·2019-08-29 11:17