Python 效能分析大全

2021-08-10 11:48:02 字數 4804 閱讀 1981

雖然執行速度慢是 python 與生俱來的特點,大多數時候我們用 python 就意味著放棄對效能的追求。但是,就算是用純 python 完成同乙個任務,老手寫出來的**可能會比菜鳥寫的**塊幾倍,甚至是幾十倍(這裡不考慮演算法的因素,只考慮語言方面的因素)。很多時候,我們將自己的**執行緩慢地原因歸結於python本來就很慢,從而心安理得地放棄深入**。

但是,事實真的是這樣嗎?面對python**,你有分析下面這些問題嗎:

程式執行的速度如何?

程式執行時間的瓶頸在**?

能否稍加改進以提高執行速度呢?

為了更好了解python程式,我們需要一套工具,能夠記錄**執行時間,生成乙個效能分析報告,方便徹底了解**,從而進行針對性的優化(本篇側重於**效能分析,不關注如何優化)。

誰快誰慢

假設有乙個字串,想將裡面的空格替換為字元『-』,用python實現起來很簡單,下面是四種方案:

def slow_replace():

replace_str = ""

for i, char in enumerate(orignal_str):

c = char if char != " " else "-"

replace_str += c

return replace_str

def fast_replace():

return "-".join(orignal_str.split())

def fastest_replace():

return orignal_str.replace(" ", "-")

這四種方案的效率如何呢,哪種方案比較慢呢?這是乙個問題!

時間斷點

最直接的想法是在開始 replace 函式之前記錄時間,程式結束後再記錄時間,計算時間差即為程式執行時間。python提供了模組 time,其中 time.clock() 在unix/linux下返回的是cpu時間(浮點數表示的秒數),win下返回的是以秒為單位的真實時間(wall-clock time)。

由於替換函式耗時可能非常短,所以這裡考慮分別執行 100000次,然後檢視不同函式的效率。我們的效能分析輔助函式如下:

def _time_analyze_(func):

from time import clock

start = clock()

for i in range(exec_times):

func()

finish = clock()

print " s".format(func.__name__ + ":", finish - start)

這樣就可以了解上面程式的執行時間情況:

第一種方案耗時是第四種的 45 倍多,大跌眼鏡了吧!同樣是 python**,完成一樣的功能,耗時可以差這麼多。

為了避免每次在程式開始、結束時插入時間斷點,然後計算耗時,可以考慮實現乙個上下文管理器,具體**如下:

class timer(object):

def __init__(self, verbose=false):

self.verbose = verbose

def __enter__(self):

self.start = clock()

return self

def __exit__(self, *args):

self.end = clock()

self.secs = self.end - self.start

self.msecs = self.secs * 1000  # millisecs

if self.verbose:

print 'elapsed time: %f ms' % self.msecs

使用時只需要將要測量時間的**段放進 with 語句即可,具體的使用例子放在 gist 上。

timeit

上面手工插斷點的方法十分原始,用起來不是那麼方便,即使用了上下文管理器實現起來還是略顯笨重。還好 python 提供了timeit模組,用來測試**塊的執行時間。它既提供了命令列介面,又能用於**檔案之中。

命令列介面

命令列介面可以像下面這樣使用:

$ python -m timeit -n 1000000 '"i like to reading.".replace(" ", "-")'

1000000 loops, best of 3: 0.253 usec per loop

$ python -m timeit -s 'orignal_str = "i like to reading."' '"-".join(orignal_str.split())'

1000000 loops, best of 3: 0.53 usec per loop

具體引數使用可以用命令 python -m timeit -h 檢視幫助。使用較多的是下面的選項:

-s s, –setup=s: 用來初始化statement中的變數,只執行一次;

-n n, –number=n: 執行statement的次數,缺省會選擇乙個合適的數字;

-r n, –repeat=n: 重複測試的次數,預設為3;

python 介面

可以用下面的程式測試四種 replace函式的運**況(完整的測試程式可以在 gist 上找到):

def _timeit_analyze_(func):

from timeit import timer

t1 = timer("%s()" % func.__name__, "from __main__ import %s" % func.__name__)

print " s".format(func.__name__ + ":", t1.timeit(exec_times))

執行結果如下:

python的timeit提供了 timeit.timer() 類,類構造方法如下:

timer(stmt='pass', setup='pass', timer=)

其中:stmt: 要計時的語句或者函式;

setup: 為stmt語句構建環境的匯入語句;

timer: 基於平台的時間函式(timer function);

timer()類有三個方法:

timeit(number=1000000): 返回stmt執行number次的秒數(float);

repeat(repeat=3, number=1000000): repeat為重複整個測試的次數,number為執行stmt的次數,返回以秒記錄的每個測試迴圈的耗時列表;

print_exc(file=none): 列印stmt的跟蹤資訊。

此外,timeit 還提供了另外三個函式方便使用,引數和 timer 差不多。

timeit.timeit(stmt='pass', setup='pass', timer=, number=1000000)

timeit.repeat(stmt='pass', setup='pass', timer=, repeat=3, number=1000000)

timeit.default_timer()

profile

以上方法適用於比較簡單的場合,更複雜的情況下,可以用標準庫裡面的profile或者cprofile,它可以統計程式裡每乙個函式的執行時間,並且提供了視覺化的報表。大多情況下,建議使用cprofile,它是profile的c實現,適用於執行時間長的程式。不過有的系統可能不支援cprofile,此時只好用profile。

可以用下面程式測試 timeit_profile() 函式執行時間分配情況。

import cprofile

from time_profile import *

cprofile.run("timeit_profile()")

這        樣的輸出可能會很長,很多時候我們感興趣的可能只有耗時最多的幾個函式,這個時候先將cprofile 的輸出儲存到診斷檔案中,然後用 pstats 定製更加有好的輸出(完整**在 gist 上)。

cprofile.run("timeit_profile()", "timeit")

p = pstats.stats('timeit')

p.sort_stats('time')

p.print_stats(6)

如果覺得 pstas 使用不方便,還可以使用一些圖形化工具,比如 gprof2dot 來視覺化分析 cprofile 的診斷結果。

vprof

vprof 也是乙個不錯的視覺化工具,可以用來分析 python 程式執行時間情況。

line_profiler

上面的測試最多統計到函式的執行時間,很多時候我們想知道函式裡面每一行**的執行效率,這時候就可以用到 line_profiler 了。

line_profiler 的使用特別簡單,在需要監控的函式前面加上 @profile 裝飾器。然後用它提供的 kernprof -l -v [source_code.py] 行進行診斷。下面是乙個簡單的測試程式 line_profile.py:

from time_profile import slow_replace, slowest_replace

for i in xrange(10000):

slow_replace()

slowest_replace()

輸出每列的含義如下:

line #: 行號

hits: 當前行執行的次數.

time: 當前行執行耗費的時間,單位為 「timer unit:」

per hit: 平均執行一次耗費的時間.

% time: 當前行執行時間佔總時間的比例.

line contents: 當前行的**

line_profiler 執行時間的估計不是特別精確,不過可以用來分析當前函式中哪些行是瓶頸。

python效能分析

python的效能分析工具cprofile使用起來比較簡單,如下 python m cprofile o profile.txt py arg1 arg2 其中,py是要分析的python程式入口函式,後面跟的就是對應的引數了。生成的profile.txt是儲存的profile資訊,可以用下面的py...

Python學習 python效能分析

python profiler效能分析 一種方法 if name main import profile profile.run foo 另一種命令列方法 python m profile prof1.py profile的統計結果分為ncalls,tottime,percall,cumtime,p...

python 效能分析profile

如果希望對程式進行優化,那麼效能分析是必不可少的。標準庫中包含了乙個叫profile的模組,使用起來非常簡單 importprofile,my math profile.run my math.square 100 只需要執行該模組的run方法,需要注意的是引數為字串。即可得到如下結果 4 func...