基準測試和效能分析
在本章中,我們將學習基準測試和效能分析如何幫助解決效能問題。
假設我們編寫了一個程式碼,它也給出了預期的結果,但如果我們想讓這個程式碼執行得更快一些,因為需求發生了變化。在這種情況下,我們需要找出程式碼的哪些部分正在減慢整個程式的速度。在這種情況下,基準測試和效能分析可能會有用。
什麼是基準測試?
基準測試旨在透過與標準進行比較來評估某事。但是,這裡出現的問題是,基準測試是什麼,以及在軟體程式設計的情況下為什麼我們需要它。程式碼基準測試意味著程式碼執行的速度以及瓶頸在哪裡。基準測試的一個主要原因是它優化了程式碼。
基準測試是如何工作的?
如果我們談論基準測試的工作原理,我們需要從將整個程式作為當前狀態進行基準測試開始,然後我們可以結合微基準測試,然後將程式分解成更小的程式。為了找到程式中的瓶頸並對其進行最佳化。換句話說,我們可以將其理解為將大型難題分解成一系列更小、更簡單的難題,以便對其進行最佳化。
Python 基準測試模組
在 Python 中,我們有一個預設的基準測試模組,稱為timeit。藉助timeit模組,我們可以測量主程式中一小段 Python 程式碼的效能。
示例
在以下 Python 指令碼中,我們匯入了timeit模組,該模組進一步測量執行兩個函式——functionA 和functionB——所需的時間。
import timeit
import time
def functionA():
print("Function A starts the execution:")
print("Function A completes the execution:")
def functionB():
print("Function B starts the execution")
print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)
執行上述指令碼後,我們將獲得如下所示的兩個函式的執行時間。
輸出
Function A starts the execution: Function A completes the execution: 0.0014599495514175942 Function B starts the execution Function B completes the execution 0.0017024724827479076
使用裝飾器函式編寫我們自己的計時器
在 Python 中,我們可以建立我們自己的計時器,它就像timeit模組一樣工作。這可以透過裝飾器函式來完成。以下是一個自定義計時器的示例:
import random
import time
def timer_func(func):
def function_timer(*args, **kwargs):
start = time.time()
value = func(*args, **kwargs)
end = time.time()
runtime = end - start
msg = "{func} took {time} seconds to complete its execution."
print(msg.format(func = func.__name__,time = runtime))
return value
return function_timer
@timer_func
def Myfunction():
for x in range(5):
sleep_time = random.choice(range(1,3))
time.sleep(sleep_time)
if __name__ == '__main__':
Myfunction()
上面的 Python 指令碼有助於匯入隨機時間模組。我們建立了 timer_func() 裝飾器函式。它內部包含 function_timer() 函式。現在,巢狀函式將在呼叫傳入的函式之前獲取時間。然後它等待函式返回並獲取結束時間。這樣,我們最終可以讓 Python 指令碼列印執行時間。該指令碼將生成如下所示的輸出。
輸出
Myfunction took 8.000457763671875 seconds to complete its execution.
什麼是效能分析?
有時程式設計師希望測量程式的一些屬性,例如記憶體使用情況、時間複雜度或特定指令的使用情況,以測量該程式的真實能力。對程式進行此類測量稱為效能分析。效能分析使用動態程式分析來進行此類測量。
在後續部分,我們將學習有關不同 Python 效能分析模組的資訊。
cProfile – 內建模組
cProfile 是 Python 的一個內建效能分析模組。該模組是一個 C 擴充套件,開銷合理,使其適合於分析長時間執行的程式。執行後,它會記錄所有函式和執行時間。它非常強大,但有時有點難以解釋和操作。在下面的示例中,我們在以下程式碼上使用 cProfile:
示例
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target=taskofThread, args=(lock,))
t2 = threading.Thread(target= taskofThread, args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
以上程式碼儲存在thread_increment.py檔案中。現在,在命令列上使用 cProfile 執行程式碼,如下所示:
(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
3577 function calls (3522 primitive calls) in 1.688 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
… … … …
從以上輸出可以看出,cProfile 列印了所有 3577 個呼叫的函式,以及每個函式花費的時間以及呼叫次數。以下是我們在輸出中獲得的列:
ncalls - 它表示呼叫的次數。
tottime - 它表示在給定函式中花費的總時間。
percall - 它指的是 tottime 除以 ncalls 的商。
cumtime - 它表示在此函式及其所有子函式中花費的累積時間。對於遞迴函式來說,它甚至更準確。
percall - 它指的是 cumtime 除以原始呼叫的商。
filename:lineno(function) - 它基本上提供了每個函式的相應資料。