Python 應(yīng)用剖析工具介紹

【編者按】本文作者為來(lái)自 HumanGeo 的工程師 Davis,主要介紹了用于 Python 應(yīng)用性能分析的幾個(gè)工具滞详。由國(guó)內(nèi) ITOM 管理平臺(tái) OneAPM 編譯呈現(xiàn)。

HumanGeo葫督,我們廣泛使用 Python 進(jìn)行編程唆途,并且樂(lè)趣無(wú)窮。用 Python 寫的程序不僅整潔美觀缀辩,而且運(yùn)行速度快得驚人工碾。不論是私底下還是工作中弱睦,Python 都是筆者最愛(ài)的語(yǔ)言。然而渊额,即便是 Python 這樣美妙的語(yǔ)言况木,卻也可能出現(xiàn)運(yùn)行緩慢的情況。幸運(yùn)的是旬迹,有許多不錯(cuò)的工具火惊,可以幫助我們分析 Python 代碼,從而保證其運(yùn)行效率奔垦。

當(dāng)筆者剛開始在 HumanGeo 工作時(shí)屹耐,就曾遇到過(guò)一個(gè)運(yùn)行一次耗時(shí)數(shù)小時(shí)的程序,而筆者的任務(wù)椿猎,就是找出其性能瓶頸惶岭,再盡可能地提高其運(yùn)行效率。當(dāng)時(shí)犯眠,筆者使用了許多工具按灶,包括 cProfilePyCallGraph(源碼)筐咧,甚至 PyPy(一個(gè)運(yùn)行快速的 Python 解釋器)鸯旁,以確定最佳的程序優(yōu)化方案。在本文中量蕊,筆者將介紹上述工具(為了保持生產(chǎn)環(huán)境中的解釋器一致性铺罢,本文將不會(huì)介紹 PyPy 工具)的使用方法。甚至即便是最老練的開發(fā)者危融,也可以借助這些工具進(jìn)一步優(yōu)化他們的代碼畏铆。

免責(zé)聲明:不要過(guò)早地進(jìn)行優(yōu)化!有關(guān)過(guò)早優(yōu)化的詳細(xì)分析請(qǐng)查閱本文吉殃。

工具

閑話少敘辞居,下面開始介紹分析 Python 代碼的幾種便捷工具。

cProfile

CPython distribution 自帶兩種分析工具:profilecProfile蛋勺。兩者使用同樣的 API瓦灶,按理說(shuō)運(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())

在上面的代碼中撮胧,筆者通過(guò)調(diào)用 time.sleep 方法桨踪,模擬一個(gè)運(yùn)行時(shí)間很長(zhǎng)的程序,并假定運(yùn)行結(jié)果很重要芹啥。接下來(lái)锻离,對(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(<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}

我們發(fā)現(xiàn)墓怀,分析結(jié)果相當(dāng)瑣碎汽纠。其實(shí),可以用更有益的方式組織分析結(jié)果傀履。在上例中虱朵,調(diào)用列表是按照字母順序排列的,這對(duì)我們并無(wú)價(jià)值啤呼。筆者更愿意看到按照調(diào)用次數(shù)或累計(jì)運(yùn)行時(shí)間排列的調(diào)用情況卧秘。幸運(yùn)的是呢袱,通過(guò) -s 參數(shù)就能實(shí)現(xiàn)這一點(diǎn)官扣。我們馬上就能看到存在問(wè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(<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}

果然羞福!我們發(fā)現(xiàn)惕蹄,存在問(wèn)題的代碼就在 expensive 函數(shù)當(dāng)中。該函數(shù)在執(zhí)行結(jié)束之前調(diào)用了多次 time.sleep 方法治专,因此導(dǎo)致了程序的速度下降卖陵。

-s參數(shù)的有效取值列表可以在此 Python 文檔中找到。如果你想將分析結(jié)果保存到一個(gè)文件中张峰,記得使用輸出選項(xiàng) -o泪蔫。

基本功能介紹完畢之后,讓我們來(lái)看看使用分析工具查找問(wèn)題代碼的其他方法喘批。

PyCallGraph

PyCallGraph 可以看做是 cProfile 的可視化擴(kuò)展工具撩荣。借助該工具,我們可以通過(guò)出色的 Graphviz 圖片了解代碼執(zhí)行的路徑饶深。PyCallGraph 并未包含在標(biāo)準(zhǔn)的 Python 安裝包內(nèi)餐曹,因此,需要通過(guò)如下語(yǔ)句敌厘,進(jìn)行簡(jiǎn)單的安裝:

-> % pip install pycallgraph

通過(guò)下面的指令台猴,就能運(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 分析過(guò)了)曹步。結(jié)果中的數(shù)據(jù)應(yīng)該與 cProfile 提供的結(jié)果一致。不過(guò)休讳,PyCallGraph 的優(yōu)點(diǎn)在于箭窜,它能展示被調(diào)用函數(shù)相互間的關(guān)系。

讓我們來(lái)看看圖片到底長(zhǎng)什么樣:

Python 應(yīng)用剖析工具介紹

這多方便把苄取磺樱!圖片顯示了程序的運(yùn)行路徑,告訴我們程序經(jīng)歷過(guò)的每個(gè)函數(shù)婆咸、模塊以及文件竹捉,還帶有運(yùn)行時(shí)間與調(diào)用次數(shù)等信息。如果在龐大的應(yīng)用中運(yùn)行該分析工具尚骄,會(huì)得到一張巨大的圖片块差。但是,根據(jù)顏色的差別倔丈,我們?nèi)阅茌p易找到存在問(wèn)題的代碼塊憨闰。下面是 PyCallGraph 文檔中提供的一張圖片,展示了一段復(fù)雜的正則表達(dá)式調(diào)用中代碼的運(yùn)行路徑:

Python 應(yīng)用剖析工具介紹

點(diǎn)此獲取此圖分析的源碼

這些信息有什么用鹉动?

一旦我們確定了導(dǎo)致問(wèn)題代碼的根源,就可以選擇合適的解決方案優(yōu)化代碼泽示,為其提速蜜氨。下面械筛,讓我們根據(jù)特定的情況,探討一些緩慢代碼可行的解決方案飒炎。

I/O

如果你發(fā)現(xiàn)自己的代碼嚴(yán)重依賴于輸入/輸出,譬如赤赊,需要發(fā)送很多 Web 請(qǐng)求,那么怒竿,Python 的標(biāo)準(zhǔn)線程模塊或許就能幫你解決該問(wèn)題砍鸠。由于 CPython 的全局鎖機(jī)制(Global Interpreter Lock,GIL)不允許為代碼中心任務(wù)同時(shí)使用多個(gè)核爷辱,非 I/O 相關(guān)的線程并不適合用 Python 實(shí)現(xiàn)。

正則表達(dá)式

人們都說(shuō)饭弓,一旦你決定用正則表達(dá)式解決某個(gè)問(wèn)題弟断,你就有兩個(gè)問(wèn)題要解決了。正則表達(dá)式真的很難用對(duì)阀趴,而且難以維護(hù)。關(guān)于這一點(diǎn)棚菊,筆者可以寫一篇長(zhǎng)篇大論進(jìn)行闡述叔汁。(但是,我不會(huì)寫的:)码邻。正則表達(dá)式真的不簡(jiǎn)單另假,我相信有很多博文已經(jīng)做了詳盡的闡述。)不過(guò)开睡,在此苟耻,筆者將介紹幾個(gè)有用的技巧:

  1. 避免使用 .*扶檐,貪婪的匹配所有運(yùn)算符運(yùn)行起來(lái)非常慢凶杖,盡可能使用字符類才是更好的選擇款筑。
  2. 避免使用正則表達(dá)式奈梳!其實(shí),許多正則表達(dá)式都可以用簡(jiǎn)單的字符串方法替代漆撞,比如 str.startswithstr.endswith
    方法。閱讀 str 文檔可以找到更多有用的信息浮驳。
  3. 多使用 re.VERBOSE至会!Python 的正則表達(dá)式引擎非常強(qiáng)大,超級(jí)有用宵蛀,一定要好好利用县貌!

以上是有關(guān)正則表達(dá)式筆者想說(shuō)的全部?jī)?nèi)容。如果你想要更多信息瞳别,相信網(wǎng)絡(luò)上還有很多好的文章杭攻。

Python 代碼

以筆者之前剖析過(guò)的代碼為例,我們的 Python 函數(shù)會(huì)運(yùn)行成千上萬(wà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)目來(lái)說(shuō)扔茅,這是極大的速度提升召娜。而且代碼運(yùn)行起來(lái)也沒(méi)有出現(xiàn)故障。

免責(zé)聲明:請(qǐng)確保該方法只用于 pure 函數(shù)秸讹!如果將記憶(memoization)用于帶有副作用(譬如:I/O)的函數(shù)雅倒,緩存可能無(wú)法達(dá)到預(yù)期的效果。

其他情況

如果你的代碼無(wú)法使用記憶(memoization)技巧劣欢,你的算法也不像 O(n!) 這樣瘋狂裁良,或者代碼的剖析結(jié)果也沒(méi)有引人注意的地方价脾,這可能說(shuō)明你的代碼并不存在顯著的問(wèn)題。這時(shí)候犀变,你可以嘗試一下別的運(yùn)行環(huán)境或語(yǔ)言秋柄。PyPy 就是一個(gè)好的選擇,你可能還要將算法用C語(yǔ)言擴(kuò)展方法重寫一下映琳。幸運(yùn)的是蜘拉,筆者之前的項(xiàng)目并未走到這一步有鹿,但是這仍是很好的排錯(cuò)方案葱跋。

結(jié)論

剖析代碼可以幫助你理解項(xiàng)目的執(zhí)行流程源梭、找出潛在的問(wèn)題代碼稍味,以及作為開發(fā)者該如何提升程序運(yùn)行速度模庐。Python 剖析工具不但功能強(qiáng)大,簡(jiǎn)單易用怜姿,而且足夠深入以快速找出問(wèn)題根源疼燥。雖然 Python 并不是以快速著稱的語(yǔ)言,但這并不意味著你的代碼應(yīng)該拖拖拉拉但狭。管理好自己的算法撬即,適時(shí)進(jìn)行剖析,但絕不要過(guò)早優(yōu)化息罗!

OneAPM 能夠幫你查看 Python 應(yīng)用程序的方方面面迈喉,不僅能夠監(jiān)控終端的用戶體驗(yàn)温圆,還能監(jiān)控服務(wù)器性能,同時(shí)還支持追蹤數(shù)據(jù)庫(kù)得运、第三方 API 和 Web 服務(wù)器的各種問(wèn)題锅移。想閱讀更多技術(shù)文章非剃,請(qǐng)?jiān)L問(wèn) OneAPM 官方技術(shù)博客

本文轉(zhuǎn)自 OneAPM 官方博客

原文地址:http://blog.thehumangeo.com/2015/07/28/profiling-in-python/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末券坞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宇驾,更是在濱河造成了極大的恐慌猴伶,老刑警劉巖蜗顽,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雇盖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡贸街,警方通過(guò)查閱死者的電腦和手機(jī)狸相,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門脓鹃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人娇跟,你說(shuō)我怎么就攤上這事太颤。” “怎么了吃谣?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵岗憋,是天一觀的道長(zhǎng)菇用。 經(jīng)常有香客問(wèn)我,道長(zhǎng)杂穷,這世上最難降的妖魔是什么耐量? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任滤港,我火速辦了婚禮溅漾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屁倔。我一直安慰自己暮胧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著席舍,像睡著了一般来颤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上东且,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天本讥,我揣著相機(jī)與錄音拷沸,去河邊找鬼。 笑死秧了,一個(gè)胖子當(dāng)著我的面吹牛序无,可吹牛的內(nèi)容都是我干的衡创。 我是一名探鬼主播璃氢,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼狮辽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼喉脖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起舆蝴,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤须误,失蹤者是張志新(化名)和其女友劉穎仇轻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祭椰,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡方淤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年携茂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诅岩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖式廷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蝗肪,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布妻导,位于F島的核電站,受9級(jí)特大地震影響术浪,放射性物質(zhì)發(fā)生泄漏寿酌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一硕并、第九天 我趴在偏房一處隱蔽的房頂上張望倔毙。 院中可真熱鬧乙濒,春花似錦、人聲如沸么库。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至缭受,卻和暖如春该互,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔓搞。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工喂分, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甘萧。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓扬卷,卻偏偏與公主長(zhǎng)得像酸钦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子徒恋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,293評(píng)論 25 707
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,583評(píng)論 1 118
  • 看圖說(shuō)話河爹,理性看盤。 昨日我們給各位提到夷恍,牛媳维,來(lái)了!各位要做的是“坐定定指黎,有錢手莸ぃ”。今天不漲也許各位心理會(huì)有點(diǎn)慌吓揪。...
    石投記閱讀 245評(píng)論 0 0
  • 最近睡覺(jué)不是特別好柠辞。 昨天困得很早团秽,9點(diǎn)多關(guān)燈,十點(diǎn)多就睡著了叭首。 三點(diǎn)多醒了习勤,因?yàn)閴?mèng)到了你 我夢(mèng)到我把你弄丟了,看...
    Vera微辣閱讀 218評(píng)論 0 0
  • 今年急急忙忙的報(bào)名學(xué)車放棒,結(jié)果練到科三不愿意再練了姻报,看到了很多為蠅頭小利計(jì)較的婦女,和她們?cè)谝粔K很累间螟,所謂“三觀不合...
    hi微笑向暖閱讀 126評(píng)論 0 1