Python 應用剖析工具介紹
【編者按】本文作者為來自 HumanGeo 的工程師 Davis,主要介紹了用於 Python 應用效能分析的幾個工具。由國內 ITOM 管理平臺 OneAPM 編譯呈現。
在 HumanGeo,我們廣泛使用 Python 進行程式設計,並且樂趣無窮。用 Python 寫的程式不僅整潔美觀,而且執行速度快得驚人。不論是私底下還是工作中,Python 都是筆者最愛的語言。然而,即便是 Python 這樣美妙的語言,卻也可能出現執行緩慢的情況。幸運的是,有許多不錯的工具,可以幫助我們分析 Python 程式碼,從而保證其執行效率。
當筆者剛開始在 HumanGeo 工作時,就曾遇到過一個執行一次耗時數小時的程式,而筆者的任務,就是找出其效能瓶頸,再儘可能地提高其執行效率。當時,筆者使用了許多工具,包括 cProfile, PyCallGraph(原始碼),甚至 PyPy(一個執行快速的 Python 直譯器),以確定最佳的程式優化方案。在本文中,筆者將介紹上述工具(為了保持生產環境中的直譯器一致性,本文將不會介紹 PyPy 工具)的使用方法。甚至即便是最老練的開發者,也可以藉助這些工具進一步優化他們的程式碼。
免責宣告:不要過早地進行優化!有關過早優化的詳細分析請查閱本文。
工具
閒話少敘,下面開始介紹分析 Python 程式碼的幾種便捷工具。
cProfile
CPython 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(<module>)
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(<module>)
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(<module>)
1 0.003 0.003 20.028 20.028 slow.py:2(<module>)
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
。
基本功能介紹完畢之後,讓我們來看看使用分析工具查詢問題程式碼的其他方法。
PyCallGraph
PyCallGraph 可以看做是 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 能夠幫你檢視 Python 應用程式的方方面面,不僅能夠監控終端的使用者體驗,還能監控伺服器效能,同時還支援追蹤資料庫、第三方 API 和 Web 伺服器的各種問題。想閱讀更多技術文章,請訪問 OneAPM 官方技術部落格。
本文轉自 OneAPM 官方部落格
原文地址:http://blog.thehumangeo.com/2015/07/28/profiling-in-python/
相關文章
- Python 偏函式介紹及應用Python函式
- 【Python】supervisor 工具介紹Python
- Sqlite 介紹及應用SQLite
- 滲透測試工具多個應用場合介紹
- less的介紹和應用
- 應急響應工具介紹的三個系列
- SAP Fiori應用索引大全工具和 SAP Fiori Tools 的使用介紹索引
- Web應用安全測試前期情報收集方法與工具的介紹Web
- Android 應用程式元件介紹Android元件
- Lucene介紹及簡單應用
- Rsync原理介紹及配置應用
- RPM 的介紹和應用
- cpio工具介紹
- Python介紹和基礎運用Python
- Redis HyperLogLog介紹及應用Redis
- Disruptor的簡單介紹與應用
- 正交多項式介紹及應用
- call、apply、bind應用的介紹APP
- Azure Container App(一)應用介紹AIAPP
- 經典資料分析應用介紹
- arguments的應用示例簡單介紹
- Zookeeper 介紹及典型應用場景
- Flume架構以及應用介紹[轉]架構
- Xamarin開發Anroid應用介紹
- 應用oracle flashback--Flashback Database介紹OracleDatabase
- SAP作業型別應用介紹型別
- Harmony 應用開發常用元件介紹元件
- Kafka剖析:Kafka背景及架構介紹Kafka架構
- uml建模工具介紹
- Oracle BBED 工具介紹Oracle
- 【MySQL】mydumper工具介紹MySql
- Oracle DBV 工具 介紹Oracle
- 【Oracle】Opatch 工具介紹Oracle
- etcdctl工具介紹
- 爬蟲開發python工具包介紹 (2)爬蟲Python
- JavaScript內建物件介紹(重點介紹Math(),Date(),Array()以及案例應用)JavaScript物件
- Castle Windsor常用介紹以及其在ABP專案的應用介紹AST
- Redis 地理空間(geospatial)介紹及應用Redis