雖然執行速度慢是 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...