在執行復雜的Python程式時,執行時間會很長,這時也許想提高程式的執行效率。但該怎麼做呢?
首先,要有個工具能夠檢測程式碼中的瓶頸,例如,找到哪一部分執行時間比較長。接著,就針對這一部分進行優化。
同時,還需要控制記憶體和CPU的使用,這樣可以在另一方面優化程式碼。
因此,在這篇文章中我將介紹7個不同的Python工具,來檢查程式碼中函式的執行時間以及記憶體和CPU的使用。
1. 使用裝飾器來衡量函式執行時間
有一個簡單方法,那就是定義一個裝飾器來測量函式的執行時間,並輸出結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import time from functools import wraps def fn_timer(function): @wraps(function) def function_timer(*args, **kwargs): t0 = time.time() result = function(*args, **kwargs) t1 = time.time() print ("Total time running %s: %s seconds" % (function.func_name, str(t1-t0)) ) return result return function_timer |
接著,將這個裝飾器新增到需要測量的函式之前,如下所示:
1 2 3 |
@fn_timer def myfunction(...): ... |
例如,這裡檢測一個函式排序含有200萬個隨機數字的陣列所需的時間:
1 2 3 4 5 6 |
@fn_timer def random_sort(n): return sorted([random.random() for i in range(n)]) if __name__ == "__main__": random_sort(2000000) |
執行指令碼時,會看到下面的結果:
1 |
Total time running random_sort: 1.41124916077 seconds |
2. 使用timeit模組
另一種方法是使用timeit模組,用來計算平均時間消耗。
執行下面的指令碼可以執行該模組。
1 |
$ python -m timeit -n 4 -r 5 -s "import timing_functions" "timing_functions.random_sort(2000000)" |
這裡的timing_functions是Python指令碼檔名稱。
在輸出的末尾,可以看到以下結果:
1 |
4 loops, best of 5: 2.08 sec per loop |
這表示測試了4次,平均每次測試重複5次,最好的測試結果是2.08秒。
如果不指定測試或重複次數,預設值為10次測試,每次重複5次。
3. 使用Unix系統中的time命令
然而,裝飾器和timeit都是基於Python的。在外部環境測試Python時,unix time實用工具就非常有用。
執行time實用工具:
1 |
$ time -p python timing_functions.py |
輸出結果為:
1 2 3 4 |
Total time running random_sort: 1.3931210041 seconds real 1.49 user 1.40 sys 0.08 |
第一行來自預定義的裝飾器,其他三行為:
- real表示的是執行指令碼的總時間
- user表示的是執行指令碼消耗的CPU時間。
- sys表示的是執行核心函式消耗的時間。
注意:根據維基百科的定義,核心是一個計算機程式,用來管理軟體的輸入輸出,並將其翻譯成CPU和其他計算機中的電子裝置能夠執行的資料處理指令。
因此,Real執行時間和User+Sys執行時間的差就是消耗在輸入/輸出和系統執行其他任務時消耗的時間。
4. 使用cProfile模組
如果想知道每個函式和方法消耗了多少時間,以及這些函式被呼叫了多少次,可以使用cProfile模組。
1 |
$ python -m cProfile -s cumulative timing_functions.py |
現在可以看到程式碼中函式的詳細描述,其中含有每個函式呼叫的次數,由於使用了-s選項(累加),最終結果會根據每個函式的累計執行時間排序。
讀者會發現執行指令碼所需的總時間比以前要多。這是由於測量每個函式的執行時間這個操作本身也是需要時間。
5. 使用line_profiler模組
line_profiler模組可以給出執行每行程式碼所需佔用的CPU時間。
首先,安裝該模組:
1 |
$ pip install line_profiler |
接著,需要指定用@profile檢測哪個函式(不需要在程式碼中用import匯入模組):
1 2 3 4 5 6 7 8 |
@profile def random_sort2(n): l = [random.random() for i in range(n)] l.sort() return l if __name__ == "__main__": random_sort2(2000000) |
最好,可以通過下面的命令獲得關於random_sort2函式的逐行描述。
1 |
$ kernprof -l -v timing_functions.py |
其中-l表示逐行解釋,-v表示表示輸出詳細結果。通過這種方法,我們看到構建陣列消耗了44%的計算時間,而sort()方法消耗了剩餘的56%的時間。
同樣,由於需要檢測執行時間,指令碼的執行時間更長了。
6. 使用memory_profiler模組
memory_profiler模組用來基於逐行測量程式碼的記憶體使用。使用這個模組會讓程式碼執行的更慢。
安裝方法如下:
1 |
$ pip install memory_profiler |
另外,建議安裝psutil包,這樣memory_profile會執行的快一點:
1 |
$ pip install psutil |
與line_profiler相似,使用@profile裝飾器來標識需要追蹤的函式。接著,輸入:
1 |
$ python -m memory_profiler timing_functions.py |
指令碼的執行時間比以前長1或2秒。如果沒有安裝psutil包,也許會更長。
從結果可以看出,記憶體使用是以MiB為單位衡量的,表示的mebibyte(1MiB = 1.05MB)。
7. 使用guppy包
最後,通過這個包可以知道在程式碼執行的每個階段中,每種型別(str、tuple、dict等)分別建立了多少物件。
安裝方法如下:
1 |
$ pip install guppy |
接著,將其新增到程式碼中:
1 2 3 4 5 6 7 8 9 10 11 12 |
from guppy import hpy def random_sort3(n): hp = hpy() print "Heap at the beginning of the functionn", hp.heap() l = [random.random() for i in range(n)] l.sort() print "Heap at the end of the functionn", hp.heap() return l if __name__ == "__main__": random_sort3(2000000) |
執行程式碼:
1 |
$ python timing_functions.py |
可以看到輸出結果為:
通過在程式碼中將heap()放置在不同的位置,可以瞭解到指令碼中的物件建立和刪除操作的流程。
如果想學習更多關於Python程式碼速度優化方面的知識,我建議你去讀這本書《High Performance Python: Practical Performant Programming for Humans, september 2014.》
希望這篇文章能偶幫到你!^_^
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式