基準測試和效能分析



在本章中,我們將學習基準測試和效能分析如何幫助解決效能問題。

假設我們編寫了一個程式碼,它也給出了預期的結果,但如果我們想讓這個程式碼執行得更快一些,因為需求發生了變化。在這種情況下,我們需要找出程式碼的哪些部分正在減慢整個程式的速度。在這種情況下,基準測試和效能分析可能會有用。

什麼是基準測試?

基準測試旨在透過與標準進行比較來評估某事。但是,這裡出現的問題是,基準測試是什麼,以及在軟體程式設計的情況下為什麼我們需要它。程式碼基準測試意味著程式碼執行的速度以及瓶頸在哪裡。基準測試的一個主要原因是它優化了程式碼。

基準測試是如何工作的?

如果我們談論基準測試的工作原理,我們需要從將整個程式作為當前狀態進行基準測試開始,然後我們可以結合微基準測試,然後將程式分解成更小的程式。為了找到程式中的瓶頸並對其進行最佳化。換句話說,我們可以將其理解為將大型難題分解成一系列更小、更簡單的難題,以便對其進行最佳化。

Python 基準測試模組

在 Python 中,我們有一個預設的基準測試模組,稱為timeit。藉助timeit模組,我們可以測量主程式中一小段 Python 程式碼的效能。

示例

在以下 Python 指令碼中,我們匯入了timeit模組,該模組進一步測量執行兩個函式——functionAfunctionB——所需的時間。

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) - 它基本上提供了每個函式的相應資料。

廣告