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