10種檢測Python程序運行時間、CPU和內(nèi)存占用的方法
在運行復(fù)雜的Python程序時,執(zhí)行時間會很長,這時也許想提高程序的執(zhí)行效率。但該怎么做呢?
首先,要有個工具能夠檢測代碼中的瓶頸,例如,找到哪一部分執(zhí)行時間比較長。接著,就針對這一部分進行優(yōu)化。
同時,還需要控制內(nèi)存和CPU的使用,這樣可以在另一方面優(yōu)化代碼。
因此,在這篇文章中我將介紹7個不同的Python工具,來檢查代碼中函數(shù)的執(zhí)行時間以及內(nèi)存和CPU的使用。
1. 使用裝飾器來衡量函數(shù)執(zhí)行時間
有一個簡單方法,那就是定義一個裝飾器來測量函數(shù)的執(zhí)行時間,并輸出結(jié)果:
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
接著,將這個裝飾器添加到需要測量的函數(shù)之前,如下所示:
@fn_timer def myfunction(...): ...
例如,這里檢測一個函數(shù)排序含有200萬個隨機數(shù)字的數(shù)組所需的時間:
@fn_timer def random_sort(n): return sorted([random.random() for i in range(n)]) if __name__ == "__main__": random_sort(2000000)
執(zhí)行腳本時,會看到下面的結(jié)果:
Total time running random_sort: 1.41124916077 seconds
2. 使用timeit模塊
另一種方法是使用timeit模塊,用來計算平均時間消耗。
執(zhí)行下面的腳本可以運行該模塊。
python -m timeit -n 4 -r 5 -s "import timing_functions" "timing_functions.random_sort(2000000)"
這里的timing_functions是Python腳本文件名稱。
在輸出的末尾,可以看到以下結(jié)果:
4 loops, best of 5: 2.08 sec per loop
這表示測試了4次,平均每次測試重復(fù)5次,最好的測試結(jié)果是2.08秒。
如果不指定測試或重復(fù)次數(shù),默認值為10次測試,每次重復(fù)5次。
3. 使用Unix系統(tǒng)中的time命令
然而,裝飾器和timeit都是基于Python的。在外部環(huán)境測試Python時,unix time實用工具就非常有用。
運行time實用工具:
$ time -p python timing_functions.py
輸出結(jié)果為:
Total time running random_sort: 1.3931210041 seconds real 1.49 user 1.40 sys 0.08
第一行來自預(yù)定義的裝飾器,其他三行為:
- real表示的是執(zhí)行腳本的總時間
- user表示的是執(zhí)行腳本消耗的CPU時間。
- sys表示的是執(zhí)行內(nèi)核函數(shù)消耗的時間。
注意:根據(jù)維基百科的定義,內(nèi)核是一個計算機程序,用來管理軟件的輸入輸出,并將其翻譯成CPU和其他計算機中的電子設(shè)備能夠執(zhí)行的數(shù)據(jù)處理指令。
因此,Real執(zhí)行時間和User+Sys執(zhí)行時間的差就是消耗在輸入/輸出和系統(tǒng)執(zhí)行其他任務(wù)時消耗的時間。
4. 使用cProfile模塊
如果想知道每個函數(shù)和方法消耗了多少時間,以及這些函數(shù)被調(diào)用了多少次,可以使用cProfile模塊。
$ python -m cProfile -s cumulative timing_functions.py
現(xiàn)在可以看到代碼中函數(shù)的詳細描述,其中含有每個函數(shù)調(diào)用的次數(shù),由于使用了-s選項(累加),最終結(jié)果會根據(jù)每個函數(shù)的累計執(zhí)行時間排序。
讀者會發(fā)現(xiàn)執(zhí)行腳本所需的總時間比以前要多。這是由于測量每個函數(shù)的執(zhí)行時間這個操作本身也是需要時間。
5. 使用line_profiler模塊
line_profiler模塊可以給出執(zhí)行每行代碼所需占用的CPU時間。
首先,安裝該模塊:
$ pip install line_profiler
接著,需要指定用@profile檢測哪個函數(shù)(不需要在代碼中用import導入模塊):
@profile def random_sort2(n): l = [random.random() for i in range(n)] l.sort() return l if __name__ == "__main__": random_sort2(2000000)
最好,可以通過下面的命令獲得關(guān)于random_sort2函數(shù)的逐行描述。
$ kernprof -l -v timing_functions.py
其中-l表示逐行解釋,-v表示表示輸出詳細結(jié)果。通過這種方法,我們看到構(gòu)建數(shù)組消耗了44%的計算時間,而sort()方法消耗了剩余的56%的時間。
同樣,由于需要檢測執(zhí)行時間,腳本的執(zhí)行時間更長了。
6. 使用memory_profiler模塊
memory_profiler模塊用來基于逐行測量代碼的內(nèi)存使用。使用這個模塊會讓代碼運行的更慢。
安裝方法如下:
pip install memory_profiler
另外,建議安裝psutil包,這樣memory_profile會運行的快一點:
$ pip install psutil
與line_profiler相似,使用@profile裝飾器來標識需要追蹤的函數(shù)。接著,輸入:
$ python -m memory_profiler timing_functions.py
腳本的執(zhí)行時間比以前長1或2秒。如果沒有安裝psutil包,也許會更長。
從結(jié)果可以看出,內(nèi)存使用是以MiB為單位衡量的,表示的mebibyte(1MiB = 1.05MB)。
7. 使用guppy包
最后,通過這個包可以知道在代碼執(zhí)行的每個階段中,每種類型(str、tuple、dict等)分別創(chuàng)建了多少對象。
安裝方法如下:
$ pip install guppy
接著,將其添加到代碼中:
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)
運行代碼:
$ python timing_functions.py
可以看到輸出結(jié)果為:
通過在代碼中將heap()放置在不同的位置,可以了解到腳本中的對象創(chuàng)建和刪除操作的流程。
如果想學習更多關(guān)于Python代碼速度優(yōu)化方面的知識,我建議你去讀這本書《High Performance Python: Practical Performant Programming for Humans, september 2014.》
希望這篇文章能偶幫到你!^_^
相關(guān)文章
利用Python實現(xiàn)文件讀取與輸入以及數(shù)據(jù)存儲與讀取的常用命令
這篇文章主要給大家介紹了關(guān)于利用Python實現(xiàn)文件讀取與輸入以及數(shù)據(jù)存儲與讀取的常用命令,文中還介紹了用python循環(huán)保存文件并循環(huán)讀取文件的方法,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-11-11