基準和性能分析

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

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

基準測試是什麼?

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

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

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