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