在本章中,我們將學習基準測試和分析如何幫助解決性能問題。
假設(shè)我們已經(jīng)編寫了一個代碼,并且它也給出了期望的結(jié)果,但是如果想要更快地運行此代碼,因為需求已經(jīng)發(fā)生了變化。 在這種情況下,需要找出代碼的哪些部分正在減慢整個程序。 在這種情況下,基準測試和分析可能很有用。
基準測試旨在通過與標準進行比較來評估某些事物。 然而,這里出現(xiàn)的問題是,什么是基準,以及為什么需要軟件編程。 對代碼進行基準測試意味著代碼的執(zhí)行速度以及瓶頸的位置。 基準測試的一個主要原因是它優(yōu)化了代碼。
基準是如何工作?
如果我們談論基準測試的工作,需要首先將整個程序作為一個當前狀態(tài),然后可以將微基準結(jié)合起來,然后將程序分解成更小的程序。 找到程序中的瓶頸并優(yōu)化它。 換句話說,我們可以把它理解為將大而難的問題分解為一系列較小和較容易的問題來優(yōu)化它們。
Python模塊進行基準測試
在Python中,我們有一個默認的基準測試模塊,稱為timeit。 在timeit模塊的幫助下,我們可以在主程序中測量一小段Python代碼的性能。
示例
在下面的Python腳本中,導入了timeit模塊,它進一步測量執(zhí)行兩個函數(shù)所需的時間 - 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)
運行上面的腳本之后,將得到兩個函數(shù)的執(zhí)行用時,如下所示。
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中,我們可以創(chuàng)建自己的計時器,它的行為就像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腳本有助于導入隨機時間模塊。 我們創(chuàng)建了timer_func()裝飾器函數(shù)。 這里面有function_timer()函數(shù)。 現(xiàn)在,嵌套函數(shù)會在調(diào)用傳入函數(shù)之前抓取時間。 然后它等待函數(shù)返回并抓取結(jié)束時間。 這樣,我們可以最終使python腳本打印執(zhí)行時間。 該腳本將生成如下所示的輸出。
Myfunction took 8.000457763671875 seconds to complete its execution.
有時程序員想要測量一些屬性,如使用內(nèi)存,時間復雜度或使用關(guān)于程序的特定指令來衡量程序的真實能力。 這種關(guān)于程序的測量稱為分析。 分析使用動態(tài)程序分析來進行這種測量。
在隨后的章節(jié)中,我們將學習用于分析的不同Python模塊。
cProfile是一個用于分析的Python內(nèi)置模塊。 該模塊是一個具有合理開銷的C擴展,適合分析長時間運行的程序。 運行后,它會記錄所有的功能和執(zhí)行時間。 這是非常強大的,但有時難以解釋和操作。 在下面的例子中,我們在下面的代碼中使用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文件中。 現(xiàn)在,在命令行上用cProfile執(zhí)行代碼如下 -
(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打印出所有被調(diào)用的3577個函數(shù),每個函數(shù)花費的時間和調(diào)用的次數(shù)。 以下是我們在輸出中獲得的列 -
ncalls - 這是要調(diào)用的數(shù)字值。tottime - 這是在給定函數(shù)中花費的總時間。percall - 它指的是tottime除以ncalls的商。cumtime - 這是在這個和所有子功能中累計的時間。 遞歸函數(shù)甚至是準確的。percall - 它是cumtime除以原始調(diào)用的商。filename:lineno(function) - 它基本上提供了每個函數(shù)的相應數(shù)據(jù)。