[英]Is there any simple way to benchmark Python script?
通常我使用 shell 命令time
。 我的目的是測試數據是小、中、大還是非常大的集合,需要多少時間和 memory 的使用量。
Linux 或僅 Python 的任何工具可以做到這一點?
看看timeit 、 python 分析器和pycallgraph 。 還要確保看看下面nikicc
提到“ SnakeViz ” 的評論。 它為您提供了另一個有用的分析數據可視化。
def test():
"""Stupid test function"""
lst = []
for i in range(100):
lst.append(i)
if __name__ == '__main__':
import timeit
print(timeit.timeit("test()", setup="from __main__ import test"))
# For Python>=3.5 one can also write:
print(timeit.timeit("test()", globals=locals()))
本質上,您可以將 python 代碼作為字符串參數傳遞給它,它會以指定的次數運行並打印執行時間。 文檔中的重要部分:
timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)
使用給定的語句、設置代碼和定時器函數創建一個Timer
實例並運行它的timeit
方法與數字處決。 可選的globals參數指定要在其中執行代碼的命名空間。
... 和:
Timer.timeit(number=1000000)
主語句執行的時間數。 這將執行一次 setup 語句,然后返回多次執行主語句所需的時間,以秒為單位作為浮點數。 參數是循環的次數,默認為一百萬。 main 語句、setup 語句和要使用的定時器函數被傳遞給構造函數。注意:默認情況下,
timeit
在計時期間暫時關閉garbage collection
。 這種方法的優點是它使獨立時序更具可比性。 這個缺點是 GC 可能是被測量函數性能的一個重要組成部分。 如果是這樣,GC 可以作為設置字符串中的第一個語句重新啟用。 例如:
timeit.Timer('for i in xrange(10): oct(i)', 'gc.enable()').timeit()
分析將使您對正在發生的事情有更詳細的了解。 這是官方文檔中的“即時示例”:
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
這會給你:
197 function calls (192 primitive calls) in 0.002 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.001 0.001 <string>:1(<module>)
1 0.000 0.000 0.001 0.001 re.py:212(compile)
1 0.000 0.000 0.001 0.001 re.py:268(_compile)
1 0.000 0.000 0.000 0.000 sre_compile.py:172(_compile_charset)
1 0.000 0.000 0.000 0.000 sre_compile.py:201(_optimize_charset)
4 0.000 0.000 0.000 0.000 sre_compile.py:25(_identityfunction)
3/1 0.000 0.000 0.000 0.000 sre_compile.py:33(_compile)
這兩個模塊都應該讓您了解在哪里尋找瓶頸。
另外,要掌握profile
的輸出,請查看這篇文章
注意pycallgraph 已於2018 年 2 月正式棄用。 截至 2020 年 12 月,它仍在使用 Python 3.6。 只要 python 公開分析 API 的方式沒有核心變化,它應該仍然是一個有用的工具。
該模塊使用 graphviz 創建如下所示的調用圖:
您可以通過顏色輕松查看哪些路徑使用時間最長。 您可以使用 pycallgraph API 或使用打包腳本創建它們:
pycallgraph graphviz -- ./mypythonscript.py
雖然開銷相當可觀。 因此,對於已經長時間運行的流程,創建圖表可能需要一些時間。
我使用一個簡單的裝飾器來計時 func
import time
def st_time(func):
"""
st decorator to calculate the total time of a func
"""
def st_func(*args, **keyArgs):
t1 = time.time()
r = func(*args, **keyArgs)
t2 = time.time()
print("Function=%s, Time=%s" % (func.__name__, t2 - t1))
return r
return st_func
timeit
模塊又慢又奇怪,所以我寫了這個:
def timereps(reps, func):
from time import time
start = time()
for i in range(0, reps):
func()
end = time()
return (end - start) / reps
例子:
import os
listdir_time = timereps(10000, lambda: os.listdir('/'))
print "python can do %d os.listdir('/') per second" % (1 / listdir_time)
對我來說,它說:
python can do 40925 os.listdir('/') per second
這是一種原始的基准測試,但已經足夠好了。
我通常會快速time ./script.py
以查看需要多長時間。 但是,這並沒有向您顯示內存,至少不是默認情況。 您可以使用/usr/bin/time -v ./script.py
獲取大量信息,包括內存使用情況。
內存分析器滿足您的所有內存需求。
https://pypi.python.org/pypi/memory_profiler
運行 pip 安裝:
pip install memory_profiler
導入庫:
import memory_profiler
為您要分析的項目添加裝飾器:
@profile
def my_func():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
del b
return a
if __name__ == '__main__':
my_func()
執行代碼:
python -m memory_profiler example.py
接收輸出:
Line # Mem usage Increment Line Contents
==============================================
3 @profile
4 5.97 MB 0.00 MB def my_func():
5 13.61 MB 7.64 MB a = [1] * (10 ** 6)
6 166.20 MB 152.59 MB b = [2] * (2 * 10 ** 7)
7 13.61 MB -152.59 MB del b
8 13.61 MB 0.00 MB return a
示例來自上面鏈接的文檔。
用於 cProfile 的snakeviz
交互式查看器
https://github.com/jiffyclub/snakeviz/
在https://stackoverflow.com/a/1593034/895245 中提到了 cProfile,在評論中提到了 snakeviz ,但我想進一步強調它。
僅通過查看cprofile
/ pstats
輸出很難調試程序性能,因為它們只能cprofile
計算每個函數的總次數。
但是,我們通常真正需要的是查看包含每次調用的堆棧跟蹤的嵌套視圖,以便輕松找到主要瓶頸。
這正是snakeviz 通過其默認的“冰柱”視圖所提供的。
首先,您必須將 cProfile 數據轉儲到二進制文件中,然后您可以在該文件上進行 snakeviz
pip install -u snakeviz
python -m cProfile -o results.prof myscript.py
snakeviz results.prof
這將打印一個指向標准輸出的 URL,您可以在瀏覽器上打開它,其中包含如下所示的所需輸出:
然后你可以:
更多面向配置文件的問題: 如何配置 Python 腳本?
安裝后,nose 是您路徑中的一個腳本,您可以在包含一些 Python 腳本的目錄中調用該腳本:
$: nosetests
這將查看當前目錄中的所有 python 文件,並將執行它識別為測試的任何函數:例如,它將名稱中帶有 test_ 一詞的任何函數識別為測試。
因此,您可以創建一個名為 test_yourfunction.py 的 Python 腳本並在其中編寫如下內容:
$: cat > test_yourfunction.py
def test_smallinput():
yourfunction(smallinput)
def test_mediuminput():
yourfunction(mediuminput)
def test_largeinput():
yourfunction(largeinput)
然后你必須跑
$: nosetest --with-profile --profile-stats-file yourstatsprofile.prof testyourfunction.py
要讀取配置文件,請使用以下 python 行:
python -c "import hotshot.stats ; stats = hotshot.stats.load('yourstatsprofile.prof') ; stats.sort_stats('time', 'calls') ; stats.print_stats(200)"
要當心timeit
很慢,要花上我的媒體處理器12秒鍾只是初始化(或者運行功能)。 你可以測試這個接受的答案
def test():
lst = []
for i in range(100):
lst.append(i)
if __name__ == '__main__':
import timeit
print(timeit.timeit("test()", setup="from __main__ import test")) # 12 second
對於簡單的事情,我將使用time
代替,在我的 PC 上它返回結果0.0
import time
def test():
lst = []
for i in range(100):
lst.append(i)
t1 = time.time()
test()
result = time.time() - t1
print(result) # 0.000000xxxx
快速測試任何函數的簡單方法是使用以下語法: %timeit my_code
例如 :
%timeit a = 1
13.4 ns ± 0.781 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
如果您不想為 timeit 編寫樣板代碼並易於分析結果,請查看benchmarkit 。 它還保存了以前運行的歷史記錄,因此很容易在開發過程中比較相同的功能。
# pip install benchmarkit
from benchmarkit import benchmark, benchmark_run
N = 10000
seq_list = list(range(N))
seq_set = set(range(N))
SAVE_PATH = '/tmp/benchmark_time.jsonl'
@benchmark(num_iters=100, save_params=True)
def search_in_list(num_items=N):
return num_items - 1 in seq_list
@benchmark(num_iters=100, save_params=True)
def search_in_set(num_items=N):
return num_items - 1 in seq_set
benchmark_results = benchmark_run(
[search_in_list, search_in_set],
SAVE_PATH,
comment='initial benchmark search',
)
打印到終端並返回包含上次運行數據的字典列表。 命令行入口點也可用。
如果您更改N=1000000
並重新運行
基於劉丹雲的回答,具有一些便利功能,也許對某人有用。
def stopwatch(repeat=1, autorun=True):
"""
stopwatch decorator to calculate the total time of a function
"""
import timeit
import functools
def outer_func(func):
@functools.wraps(func)
def time_func(*args, **kwargs):
t1 = timeit.default_timer()
for _ in range(repeat):
r = func(*args, **kwargs)
t2 = timeit.default_timer()
print(f"Function={func.__name__}, Time={t2 - t1}")
return r
if autorun:
try:
time_func()
except TypeError:
raise Exception(f"{time_func.__name__}: autorun only works with no parameters, you may want to use @stopwatch(autorun=False)") from None
return time_func
if callable(repeat):
func = repeat
repeat = 1
return outer_func(func)
return outer_func
一些測試:
def is_in_set(x):
return x in {"linux", "darwin"}
def is_in_list(x):
return x in ["linux", "darwin"]
@stopwatch
def run_once():
import time
time.sleep(0.5)
@stopwatch(autorun=False)
def run_manually():
import time
time.sleep(0.5)
run_manually()
@stopwatch(repeat=10000000)
def repeat_set():
is_in_set("windows")
is_in_set("darwin")
@stopwatch(repeat=10000000)
def repeat_list():
is_in_list("windows")
is_in_list("darwin")
@stopwatch
def should_fail(x):
pass
結果:
Function=run_once, Time=0.5005391679987952
Function=run_manually, Time=0.500624185999186
Function=repeat_set, Time=1.7064883739985817
Function=repeat_list, Time=1.8905151920007484
Traceback (most recent call last):
(some more traceback here...)
Exception: should_fail: autorun only works with no parameters, you may want to use @stopwatch(autorun=False)
pip install line_profiler
@profile
裝飾器。 例如:@profile
def function(base, index, shift):
addend = index << shift
result = base + addend
return result
kernprof -l <file_name>
創建 line_profiler 的實例。 例如:kernprof -l test.py
kernprof 將在成功時將Wrote profile results to <file_name>.lprof
打印Wrote profile results to <file_name>.lprof
。 例如:
Wrote profile results to test.py.lprof
python -m line_profiler <file_name>.lprof
打印基准測試結果。 例如:python -m line_profiler test.py.lprof
您將看到有關每行代碼的詳細信息:
Timer unit: 1e-06 s
Total time: 0.0021632 s
File: test.py
Function: function at line 1
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 @profile
2 def function(base, index, shift):
3 1000 796.4 0.8 36.8 addend = index << shift
4 1000 745.9 0.7 34.5 result = base + addend
5 1000 620.9 0.6 28.7 return result
pip install memory_profiler
@profile
裝飾器。 例如:@profile
def function():
result = []
for i in range(10000):
result.append(i)
return result
python -m memory_profiler <file_name>
打印基准測試結果。 例如:python -m memory_profiler test.py
您將看到有關每行代碼的詳細信息:
Filename: test.py
Line # Mem usage Increment Occurences Line Contents
============================================================
1 40.246 MiB 40.246 MiB 1 @profile
2 def function():
3 40.246 MiB 0.000 MiB 1 result = []
4 40.758 MiB 0.008 MiB 10001 for i in range(10000):
5 40.758 MiB 0.504 MiB 10000 result.append(i)
6 40.758 MiB 0.000 MiB 1 return result
多次調用一個函數以盡量減少對環境的影響。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.