W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
在 Python 編程過程中,性能分析是一個至關(guān)重要的環(huán)節(jié)。性能分析可以幫助我們找出程序中的瓶頸,從而優(yōu)化代碼,提高程序的運行效率。Python 標準庫提供了 cProfile
和 profile
模塊來實現(xiàn)確定性性能分析。本文將深入淺出地為您介紹這兩個模塊的使用方法,讓您輕松掌握 Python 性能分析技巧。
確定性性能分析 是指監(jiān)控程序中所有的函數(shù)調(diào)用、函數(shù)返回和異常事件,并精確計時這些事件之間的時間間隔。與統(tǒng)計分析相比,確定性分析能夠提供更詳細、準確的運行時統(tǒng)計信息,幫助我們更好地了解程序的性能狀況。
cProfile
和 profile
模塊簡介Python 提供了兩種性能分析模塊:
cProfile
:這是一個 C 擴展插件,具有較低的運行開銷,適合分析長時間運行的程序。它基于 lsprof
開發(fā),是大多數(shù)用戶的首選。profile
:這是一個純 Python 模塊,雖然運行開銷較大,但更易于擴展和定制。如果需要對分析器進行深入的二次開發(fā),這個模塊會更加合適。通常情況下,我們推薦使用 cProfile
模塊,因為它在性能分析過程中對程序運行的影響較小。
以下是使用 cProfile
模塊分析單個函數(shù)的簡單示例:
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
執(zhí)行上述代碼后,將輸出類似于以下的分析結(jié)果:
214 function calls (207 primitive calls) in 0.002 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.002 0.002 {built-in method builtins.exec}
1 0.000 0.000 0.001 0.001 <string>:1(<module>)
1 0.000 0.000 0.001 0.001 __init__.py:250(compile)
1 0.000 0.000 0.001 0.001 __init__.py:289(_compile)
1 0.000 0.000 0.000 0.000 _compiler.py:759(compile)
1 0.000 0.000 0.000 0.000 _parser.py:937(parse)
1 0.000 0.000 0.000 0.000 _compiler.py:598(_code)
1 0.000 0.000 0.000 0.000 _parser.py:435(_parse_sub)
結(jié)果解讀 :
第一行顯示共有 214 次函數(shù)調(diào)用,其中 207 次為原始調(diào)用(非遞歸調(diào)用)。
第二行表示輸出結(jié)果是按照累計時間(cumulative time)排序的。
各列含義如下:
ncalls
:函數(shù)調(diào)用次數(shù)。tottime
:在該函數(shù)中執(zhí)行所花費的總時間(不包括調(diào)用子函數(shù)的時間)。percall
:tottime
與 ncalls
的比值,表示每次調(diào)用該函數(shù)的平均時間。cumtime
:該函數(shù)及其所有子函數(shù)的累計執(zhí)行時間。percall
:cumtime
與原始調(diào)用次數(shù)的比值。filename:lineno(function)
:函數(shù)所在的文件名、行號和函數(shù)名。如果不想直接打印分析結(jié)果,可以將其保存到文件中,以便后續(xù)分析:
import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')
此時,分析結(jié)果會被保存到名為 restats
的文件中。之后可以使用 pstats.Stats
類來讀取和處理這些結(jié)果。
cProfile
和 profile
模塊還可以作為腳本直接調(diào)用,用于分析其他腳本的性能。例如:
python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)
-o output_file
:將性能分析結(jié)果保存到指定文件中,而不是輸出到標準輸出。-s sort_order
:指定排序方式,例如按時間排序(time
)、按累計時間排序(cumulative
)等。-m module
:指定要分析的模塊而不是腳本。pstats.Stats
類的使用pstats.Stats
類提供了豐富的功能,用于分析和格式化性能分析結(jié)果。
import pstats
from pstats import SortKey
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()
strip_dirs()
:從函數(shù)文件名中移除多余的路徑信息,使輸出更簡潔。sort_stats(-1)
:對分析結(jié)果進行排序,-1
表示按標準名稱排序。print_stats()
:打印分析結(jié)果。可以按不同的條件對分析結(jié)果進行排序,以便更直觀地了解程序性能。
p.sort_stats(SortKey.NAME)
p.print_stats()
此代碼按函數(shù)名稱排序并打印結(jié)果。
p.sort_stats(SortKey.CUMULATIVE).print_stats(10)
此處按累計時間排序,并打印前 10 行結(jié)果,有助于快速定位程序中的性能瓶頸。
p.sort_stats(SortKey.TIME).print_stats(10)
按每個函數(shù)自身的執(zhí)行時間排序,同樣顯示前 10 行。
可以根據(jù)特定條件篩選分析結(jié)果,例如:
p.sort_stats(SortKey.FILENAME).print_stats('__init__')
按文件名排序,并打印所有包含 __init__
的函數(shù)的分析結(jié)果。
p.sort_stats(SortKey.TIME, SortKey.CUMULATIVE).print_stats(.5, 'init')
先按時間排序,再按累計時間排序,打印出原始結(jié)果 50% 的數(shù)據(jù)中包含 init
的相關(guān)函數(shù)信息。
還可以查看函數(shù)的調(diào)用者和被調(diào)用者:
p.print_callers(.5, 'init')
顯示調(diào)用了包含 init
的函數(shù)的調(diào)用者列表。
p.print_callees()
打印被指定函數(shù)調(diào)用的所有函數(shù)列表。
profile.Profile
類的使用在需要更精確地控制性能分析過程時,可以直接使用 profile.Profile
類。
import cProfile, pstats, io
from pstats import SortKey
pr = cProfile.Profile()
pr.enable()
# 在這里執(zhí)行要分析的代碼
pr.disable()
s = io.StringIO()
sortby = SortKey.CUMULATIVE
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())
enable()
:開始收集性能分析數(shù)據(jù)。disable()
:停止收集數(shù)據(jù)。也可以使用上下文管理器來簡化代碼:
import cProfile
with cProfile.Profile() as pr:
# 在這里執(zhí)行代碼
pr.print_stats()
profile.Profile
類還提供了以下方法:
create_stats()
:停止收集數(shù)據(jù),并將結(jié)果記錄為當前 profile。print_stats(sort=-1)
:根據(jù)當前性能分析數(shù)據(jù)創(chuàng)建一個 Stats
對象并打印結(jié)果,sort
參數(shù)用于指定排序方式。dump_stats(filename)
:將性能分析結(jié)果寫入指定文件。run(cmd)
:對指定命令進行性能分析。runctx(cmd, globals, locals)
:在指定的全局和局部環(huán)境下對命令進行性能分析。runcall(func, /, *args, **kwargs)
:對函數(shù)調(diào)用進行性能分析。通過性能分析得到的數(shù)據(jù),我們可以深入理解程序的運行情況,從而進行有針對性的優(yōu)化。
重點關(guān)注 tottime
和 cumtime
較高的函數(shù),這些函數(shù)可能是程序的性能瓶頸。例如,如果某個函數(shù)的 tottime
很高,說明該函數(shù)自身的執(zhí)行效率較低,可能需要優(yōu)化其算法或?qū)崿F(xiàn)方式;如果 cumtime
較高而 tottime
相對較低,則表明該函數(shù)調(diào)用了許多其他耗時函數(shù),可能需要對整體的函數(shù)調(diào)用結(jié)構(gòu)進行優(yōu)化。
根據(jù) ncalls
列,找出調(diào)用次數(shù)過多的函數(shù)。如果一個函數(shù)被頻繁調(diào)用,但其實可以合并或減少調(diào)用次數(shù),那么優(yōu)化這部分代碼將對程序性能產(chǎn)生顯著提升。例如,將一些重復(fù)的計算移到循環(huán)外部,或者使用更高效的數(shù)據(jù)結(jié)構(gòu)來減少不必要的函數(shù)調(diào)用。
對于遞歸函數(shù),性能分析結(jié)果中的 ncalls
列可能會顯示多個調(diào)用次數(shù)(例如顯示為 3/1
),其中第一個數(shù)字是總調(diào)用次數(shù),第二個數(shù)字是原始調(diào)用次數(shù)。通過這種方式,我們可以了解遞歸函數(shù)的調(diào)用深度和頻率,從而判斷是否需要對遞歸實現(xiàn)進行優(yōu)化,或者考慮使用迭代代替遞歸以提高性能。
性能分析不是一勞永逸的工作,而是一個持續(xù)的過程。在對程序進行優(yōu)化后,再次進行性能分析,對比優(yōu)化前后的數(shù)據(jù),評估優(yōu)化效果。根據(jù)新的分析結(jié)果,繼續(xù)尋找潛在的性能瓶頸并進行優(yōu)化,通過不斷的迭代,逐步提升程序的性能表現(xiàn)。
如果需要改變時間測量方式,可以向 Profile
類構(gòu)造器傳入自定義的計時函數(shù):
import time
pr = profile.Profile(time.perf_counter)
profile.Profile
,計時函數(shù)可以返回一個數(shù)字或數(shù)字列表。cProfile.Profile
,計時函數(shù)應(yīng)返回一個數(shù)字,如果返回整數(shù),還可以通過第二個參數(shù)指定單位時間長度。為了提高性能分析的準確性,可以對 profile
模塊的性能分析器進行校準:
import profile
pr = profile.Profile()
for i in range(5):
print(pr.calibrate(10000))
校準后,可以通過以下方式應(yīng)用計算出的偏差值:
# 方法 1:應(yīng)用于所有后續(xù)創(chuàng)建的 Profile 實例
profile.Profile.bias = your_computed_bias
# 方法 2:應(yīng)用于特定的 Profile 實例
pr = profile.Profile()
pr.bias = your_computed_bias
# 方法 3:在構(gòu)造函數(shù)中指定
pr = profile.Profile(bias=your_computed_bias)
掌握 Python 的 cProfile
和 profile
模塊,能夠讓我們輕松地對程序進行性能分析,找出性能瓶頸并進行優(yōu)化。在編程獅平臺上,您可以進一步學(xué)習(xí)和實踐這些性能分析工具,提升自己的 Python 編程技能,編寫出更高效、更優(yōu)質(zhì)的代碼。通過持續(xù)的性能分析和優(yōu)化,讓您的程序在各種場景下都能表現(xiàn)出色。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: