基準和效能分析


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

假設我們已經編寫了一個程式碼,並且它也給出了期望的結果,但是如果想要更快地執行此程式碼,因為需求已經發生了變化。 在這種情況下,需要找出程式碼的哪些部分正在減慢整個程式。 在這種情況下,基準測試和分析可能很有用。

基準測試是什麼?

基準測試旨在通過與標準進行比較來評估某些事物。 然而,這裡出現的問題是,什麼是基準,以及為什麼需要軟體程式設計。 對程式碼進行基準測試意味著程式碼的執行速度以及瓶頸的位置。 基準測試的一個主要原因是它優化了程式碼。

基準是如何工作?
如果我們談論基準測試的工作,需要首先將整個程式作為一個當前狀態,然後可以將微基準結合起來,然後將程式分解成更小的程式。 找到程式中的瓶頸並優化它。 換句話說,我們可以把它理解為將大而難的問題分解為一系列較小和較容易的問題來優化它們。

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):
    """
    A timer decorator
    """
    def function_timer(*args, **kwargs):
       """
        A nested function for timing other functions
       """
       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) - 它基本上提供了每個函式的相應資料。