Python性能分析

Python的高效體現(xiàn)在它的開(kāi)發(fā)效率励饵,完善的類(lèi)庫(kù)支持,特別是這幾年在數(shù)據(jù)科學(xué)中的流行,使得Python開(kāi)始在各種語(yǔ)言排行榜獨(dú)占鰲頭,但是Python代碼運(yùn)行效率低這一點(diǎn)也一直被詬病瓦糕,要想讓Python高效運(yùn)行,掌握一套性能分析的方法和工具腋么,就顯得很重要咕娄。

一般通過(guò)profiling找到代碼瓶頸,然后針對(duì)這個(gè)瓶頸代碼進(jìn)行優(yōu)化珊擂,從而可以到達(dá)事半功倍的效果圣勒,即用最少的工作量來(lái)獲得最大的性能的提升,這在任何場(chǎng)景下都是最優(yōu)的選擇摧扇。任何可衡量的資源都可以進(jìn)行profiling圣贸,除了CPU,內(nèi)存還包括網(wǎng)絡(luò)帶寬扛稽,磁盤(pán)I/O吁峻。

profiling的目的就是通過(guò)對(duì)系統(tǒng)進(jìn)行分析,找出哪里比較慢在张,哪里消耗內(nèi)存比較多用含,哪里會(huì)引起更多的磁盤(pán)I/O或者網(wǎng)絡(luò)I/O。但是profiling通常會(huì)增加代碼的額外開(kāi)銷(xiāo)帮匾,這種開(kāi)銷(xiāo)有時(shí)候是非常大啄骇,會(huì)10x甚至100x的降低代碼運(yùn)行效率。所以profiling一般不能直接針對(duì)線上的代碼進(jìn)行瘟斜,一般是構(gòu)建一套類(lèi)線上環(huán)境缸夹,或者把需要做profiling的代碼拿出來(lái)單獨(dú)進(jìn)行分析。

Python常用的profiling方法:

Timing

最基礎(chǔ)的就是使用time.time()來(lái)計(jì)時(shí)哼转,這個(gè)方法簡(jiǎn)單有效明未,也許所有寫(xiě)過(guò)Python代碼的人都用過(guò)。

import time
...
start_time = time.time()
output = foo(a, b, c)
end_time = time.time()
secs = end_time - start_time
print foo.func_name + " took", secs, "seconds"

我們可以創(chuàng)建一個(gè)decorator使它用起來(lái)更方便壹蔓。

import time
from functools import wraps
...
def simple_profiling(fn):
    @wraps(fn)  # 對(duì)外暴露調(diào)用裝飾器函數(shù)的函數(shù)名和docstring
    def wrapped(*args, **kwargs):
        t1 = time.time()
        result = fn(*args, **kwargs)
        t2 = time.time()
        print (
            "@simple_profiling:" + fn.func_name + " took " + str(t2 - t1) + " seconds")
        return result
    return wrapped
...
@simple_profiling
def foo(a, b, c)
    ...

這個(gè)方法的優(yōu)點(diǎn)是簡(jiǎn)單趟妥,額外開(kāi)效非常低(大部分情況下可以忽略不計(jì))。但缺點(diǎn)也很明顯佣蓉,除了總用時(shí)披摄,沒(méi)有任何其他信息。

Python的timeit模塊也提供了測(cè)量小段代碼執(zhí)行時(shí)間的方法勇凭。需要注意在使用timeit模塊時(shí)疚膊,GC會(huì)被臨時(shí)關(guān)閉,所以這個(gè)可能導(dǎo)致測(cè)試結(jié)果跟真實(shí)運(yùn)行結(jié)果有差距虾标。

python -m timeit [-n N] [-r N] [-s S] [-t] [-c] [-h] [statement ...]
-n : 執(zhí)行指定語(yǔ)句的次數(shù)
-r : 重復(fù)測(cè)量的次數(shù)(默認(rèn)3次)
-s : 指定初始化代碼或構(gòu)建環(huán)境的導(dǎo)入語(yǔ)句
-t : 使用time.time() (Windows平臺(tái)以外的默認(rèn)值)
-c : 使用time.clock() (Windows平臺(tái)默認(rèn)值)

舉一個(gè)-s的例子寓盗,假設(shè)我們?cè)趖est.py里面定義了一個(gè)函數(shù)foo

python -m timeit -n 5 -r 5 -s "import test" "test.foo(a,b,c)"
5 loops, best of 5: 28.6 usec per loop

timtie還可以在IPython環(huán)境下使用:

In [6]: timeit '"-".join(map(str, xrange(100)))'
100000000 loops, best of 3: 9.75 ns per loop

另外unix系統(tǒng)也提供time工具,用來(lái)統(tǒng)計(jì)腳本執(zhí)行的時(shí)間,它的特點(diǎn)是把運(yùn)行時(shí)間分成real,user,sys三部分傀蚌。

$ time python test_time.py

real    0m0.069s
user    0m0.038s
sys     0m0.033s

cProfile

cProfile是Python標(biāo)準(zhǔn)庫(kù)中的一個(gè)模塊基显,它可以非常仔細(xì)地分析代碼執(zhí)行過(guò)程中所有函數(shù)調(diào)用的用時(shí)和次數(shù)。cProfile最簡(jiǎn)單的用法是用cProfile.run來(lái)執(zhí)行一段代碼善炫,或是用python -m cProfile myscript.py來(lái)執(zhí)行一個(gè)腳本撩幽。例如

$ python -m cProfile -s cumulative -o profile.stats test_time.py
         5 function calls in 0.232 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.232    0.232 test_time.py:3(<module>)
        1    0.018    0.018    0.231    0.231 test_time.py:3(foo)
        1    0.177    0.177    0.177    0.177 {map}
        1    0.036    0.036    0.036    0.036 {method 'join' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

整個(gè)輸出結(jié)果給出了每一個(gè)函數(shù)的耗時(shí)信息。每個(gè)列的字段含義如下:

  • ncalls: 函數(shù)被調(diào)用次數(shù)
  • tottime: 函數(shù)總耗時(shí)箩艺,子函數(shù)的執(zhí)行時(shí)間不計(jì)算在內(nèi)
  • percall: tottime / ncalls
  • cumtime: 函數(shù)加上其所有子函數(shù)的總耗時(shí)
  • percall: cumtime / ncalls

這個(gè)例子比較簡(jiǎn)單窜醉,所有輸出很少,真實(shí)的例子估計(jì)輸出會(huì)非常多艺谆,我們可以把結(jié)果通過(guò) -o profile.stats 把結(jié)果保存到文件榨惰,然后通過(guò)強(qiáng)大的pstats模塊來(lái)進(jìn)行分析。關(guān)于pstats的具體使用可以看官方文檔擂涛。
關(guān)于使用cProfile進(jìn)行性能分析時(shí)读串,推薦profilehooks,使用方法如下:

from profilehooks import profile

class SampleClass(ParentClass):
    @profile(filename="/tmp/SampleClass_do_something.stats", immediate=True, stdout=False)
    def do_something(self):
        ...

這里把輸出結(jié)果不輸出到stdout而是直接保存到/tmp/SampleClass_do_something.stats撒妈,因?yàn)槭菍?duì)長(zhǎng)期運(yùn)行的代碼進(jìn)行profiling恢暖,所以這里把immediate設(shè)置成True,表示代碼執(zhí)行完立即輸出狰右,而不是等程序結(jié)束杰捂。profile還有很多其他參數(shù),詳細(xì)請(qǐng)看github上源碼注釋profilehooks.py

如果覺(jué)得pstats使用不方便棋蚌,還可以使用一些圖形化工具嫁佳,比如gprof2dotRunSnakeRun來(lái)可視化分析cProfile的診斷結(jié)果谷暮。這兩個(gè)工具推薦一起使用蒿往,RunSnakeRun能很快發(fā)現(xiàn)那些函數(shù)執(zhí)行時(shí)間占比比較大,然后通過(guò)gprof2dot畫(huà)的函數(shù)調(diào)用圖來(lái)具體分析湿弦。

Line Profiler

我們通過(guò)cProfile定位到了具體的耗時(shí)函數(shù)瓤漏,下面就需要具體定位瓶頸出在哪行代碼,這個(gè)時(shí)候就到了Line Profiler出場(chǎng)的時(shí)候了颊埃。與cProfile相比蔬充,Line Profiler的結(jié)果更加直觀,它可以告訴你一個(gè)函數(shù)中每一行的耗時(shí)班利。Line Profiler并不在標(biāo)準(zhǔn)庫(kù)中饥漫,需要用pip來(lái)安裝。

pip install line_profiler

line_profiler的使用特別簡(jiǎn)單罗标,在需要監(jiān)控的函數(shù)前面加上@profile裝飾器庸队。然后用它提供的 kernprof -l -v source_code.py 進(jìn)行診斷积蜻。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彻消,隨后出現(xiàn)的幾起案子浅侨,更是在濱河造成了極大的恐慌,老刑警劉巖证膨,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鼓黔,居然都是意外死亡央勒,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)澳化,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)崔步,“玉大人,你說(shuō)我怎么就攤上這事缎谷【簦” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵列林,是天一觀的道長(zhǎng)瑞你。 經(jīng)常有香客問(wèn)我,道長(zhǎng)希痴,這世上最難降的妖魔是什么者甲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮砌创,結(jié)果婚禮上虏缸,老公的妹妹穿的比我還像新娘。我一直安慰自己嫩实,他們只是感情好刽辙,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著甲献,像睡著了一般宰缤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竟纳,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音缘挑,去河邊找鬼。 笑死语淘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的惶翻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纺荧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼颅筋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起议泵,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤先口,失蹤者是張志新(化名)和其女友劉穎型奥,沒(méi)想到半個(gè)月后厢汹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坑匠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年厘灼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咽瓷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闪朱,死狀恐怖钻洒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情素标,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布寓免,位于F島的核電站,受9級(jí)特大地震影響袜香,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜈首,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望欢策。 院中可真熱鬧,春花似錦、人聲如沸猜敢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)胯盯。三九已至,卻和暖如春博脑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叉趣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工疗杉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烟具。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嗡午,于是被迫代替她去往敵國(guó)和親玖翅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子割以,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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

  • 雖然運(yùn)行速度慢是 Python 與生俱來(lái)的特點(diǎn)严沥,大多數(shù)時(shí)候我們用 Python 就意味著放棄對(duì)性能的追求。但是消玄,就...
    selfboot閱讀 1,981評(píng)論 2 28
  • 【編者按】本文作者為 Bryan Helmig坟桅,主要介紹 Python 應(yīng)用性能分析的三種進(jìn)階方案。文章系國(guó)內(nèi) I...
    OneAPM_Official閱讀 1,244評(píng)論 0 1
  • 性能分析和調(diào)優(yōu)工具簡(jiǎn)介<a id="sec-1" name="sec-1"></a> 總會(huì)遇到一個(gè)時(shí)候你會(huì)想提高程...
    Nisen閱讀 929評(píng)論 0 3
  • 作者: Nisen 總會(huì)遇到一個(gè)時(shí)候你會(huì)想提高程序執(zhí)行效率,想看看哪部分耗時(shí)長(zhǎng)成為瓶頸蓬戚,想知道程序運(yùn)行時(shí)內(nèi)存和CP...
    PyChina閱讀 627評(píng)論 0 2
  • 使用性能計(jì)時(shí)器 在Nuke中打開(kāi)性能計(jì)時(shí)器時(shí)子漩,就可以通過(guò)Python讀取性能信息。就能知道每個(gè)節(jié)點(diǎn)的運(yùn)算時(shí)間幢泼。在調(diào)...
    N景波閱讀 1,104評(píng)論 0 0