python標準庫提供的cProfile/profile模塊,計時輸出信息較多。本節(jié)將介紹其他幾種精度略低但簡單易用的計時工具。根據代碼粒度不同,將其分為三類。
1.1整個程序計時
Unix/Linux系統中,可用time命令簡單地統計整個程序的耗時。例如:
[wangxiaoyuan_@localhostPyTest]$timepythonBCLineCounter.pybulk
FileLinesCodeLinesCommentLinesEmptyLinesCommentPercent
1545010437326425380.24
real0m2.803s
user0m1.124s
sys0m0.052s
統計值real表示程序運行的實際耗時,user表示程序執(zhí)行用戶態(tài)代碼(內核外)耗費的CPU時間,sys表示程序在內核態(tài)運行所耗費的CPU時間(即調用特定內核函數的耗時)。若user和sys時間之和小于real時間,表明程序為I/O密集型(I/Obound),即程序的性能問題很可能與等待I/O有關。
time命令的詳細描述參見《Linux用戶態(tài)程序計時方式詳解》。
1.2代碼片段計時
代碼片段計時分為函數計時和語句塊計時。這兩種計時均可使用Python標準庫timeit模塊,該模塊的詳細介紹參見官方幫助。
本小節(jié)將使用timeit模塊的timeit()方法,即timeit(stmt='pass',setup='pass',timer=,number=1000000)。其中,參數stmt為待計時的目標代碼;setup為執(zhí)行代碼的準備工作(通常是import之類的語句),不計入時間;timer在Windows系統中為time.clock(),Linux系統中則為time.time(),取默認值即可;number指示stmt重復執(zhí)行的次數。該方法返回執(zhí)行stmt代碼number遍所用的時間,單位為秒,float類型。
除timeit()方法外,對于特定函數的計時,可使用裝飾器(decorator);對于語句塊計時,則可使用上下文管理器(contextmanager)。
以裝飾器為例:
importfunctools,sys,time
defFuncTimer(repeats=10000):
defdecorator(func):
@functools.wraps(func)
defwrapper(*args,**kwargs):
#Windows系統中clock()粒度為毫秒,time()粒度為1/60秒;
#Unix系統中clock()粒度為1/100秒,time()精度較其更高。
ifsys.platform=="win32":
timerFunc=time.clock
else:
timerFunc=time.time
try:
startTime=timerFunc()
foriinrange(repeats):
ret=func(*args,**kwargs)
finally:#當目標函數發(fā)生異常時,仍舊輸出計時信息
endTime=timerFunc()
print'%s.%s()=>'%(func.__module__,func.__name__),
print'TimeElasped:%.3fmsec,repeated%dtime(s).'\
%(((endTime-startTime)*1000.0),repeats)
returnret
returnwrapper
returndecorator
運行如下代碼,對比自定義裝飾器FuncTimer與timeit模塊計時效果:
@FuncTimer(10)
defDecoratedFunc():
L=[]
foriinrange(100):L.append(i)
defRawFunc():
L=[]
foriinrange(100):L.append(i)
DecoratedFunc()
importtimeit;print'%.6fsec'%timeit.timeit(stmt=RawFunc,number=10)
輸出如下:
__main__.DecoratedFunc()=>TimeElasped:0.164msec,repeated10time(s).
0.000174sec
可見,計時效果非常接近。
注意,FuncTimer裝飾器內根據系統選用不同的計時器,這是考慮到time.clock()的精度因系統平臺而異。在Unix/Linux系統中,該方法返回當前所耗的CPU時間;而在Windows系統中,該方法基于Win32函數QueryPerformanceCounter(),返回從首次調用待計時函數起所經歷的掛鐘時間(wallclocktime),精度較time.time()更高。相比而言,timeit方法中使用的缺省計時器總是測量掛鐘時間,這也意味著關于某函數的計時可能會受到同一計算機上運行的其他進程的影響。
time.clock()計時器的平臺差異性參考以下示例(假定所在腳本名為Timing.py):
@FuncTimer(5)
defSqrtTiming(loops):
importmath
try:
frommathimportfsum#Python2.6+
returnfsum([math.sqrt(x)forxinrange(loops)])
exceptImportError:#Python2.5-
returnsum([math.sqrt(x)forxinrange(loops)])
@FuncTimer(1)
defSleepTiming():
time.sleep(2)
file=open(r'out.txt',"w+")
foriinrange(10000):
file.write('helloworld!')
SqrtTiming(100000)
SleepTiming()
在Windows系統控制臺和IDLEShell里的運行結果如下:
E:\PyTest>Timing.py
SqrtTiming()=>TimeElasped:150.124msec,repeated5time(s).
SleepTiming()=>TimeElasped:2155.140msec,repeated1time(s).
__main__.SqrtTiming()=>TimeElasped:151.809msec,repeated5time(s).
__main__.SleepTiming()=>TimeElasped:2185.594msec,repeated1time(s).
>>>importTiming
Timing.SqrtTiming()=>TimeElasped:148.892msec,repeated5time(s).
Timing.SleepTiming()=>TimeElasped:2223.157msec,repeated1time(s).
在Linux系統中運行結果與之類似。若將timerFunc改為time.clock(),則計時輸出為:
[wangxiaoyuan_@localhost~]$timepythonTiming.py
__main__.SqrtTiming()=>TimeElasped:320.000msec,repeated5time(s).
__main__.SleepTiming()=>TimeElasped:330.000msec,repeated1time(s).
real0m2.381s
user0m0.332s
sys0m0.019s
可見,time.sleep(2)并未計入SleepTiming()耗時,導致計時結果與real時間相差很大。
對于代碼片段計時,以上下文管理器為例:
importcontextlib,sys,time
@contextlib.contextmanager
defBlockTimer(label='Block'):
ifsys.platform=="win32":timerFunc=time.clock
else:timerFunc=time.time
startTime=timerFunc()
try:
yield
finally:
endTime=timerFunc()
print'%s=>'%label,
print'TimeElasped:%.3fmsec.'\
%((endTime-startTime)*1000.0)
基于BlockTimer測量代碼片段的示例如下:
withBlockTimer('cPickle'):
fromcPickleimportdumps,loads
s=dumps([x*2.4forxinrange(100000)])
loads(s)
withBlockTimer('json'):
fromjsonimportdumps,loads
s=dumps([x*2.4forxinrange(100000)])
loads(s)
運行結果如下:
cPickle=>TimeElasped:237.569msec.
json=>TimeElasped:181.714msec.
可見,對于浮點型對象,json模塊執(zhí)行速度比cPickle模塊更快。
當然,借助timeit模塊也可對代碼片段計時。例如:
fromtimeitimporttimeit
sep='fromcPickleimportdumps,loads'
stp='s=dumps([x*2forxinrange(100000)]);loads(s)'
print'cPickle:%.6fsec'%timeit(stmt=stp,setup=sep,number=1)
sej='fromjsonimportdumps,loads'
stj='s=dumps([x*2forxinrange(100000)]);loads(s)'
print'json:%.6fsec'%timeit(stmt=stj,setup=sej,number=1)
本例改為整型對象,且模塊導入語句不計入總耗時。運行結果如下:
cPickle:0.100775sec
json:0.064752sec
可見,對于整型對象,json模塊執(zhí)行速度也比cPickle模塊快。
以上內容為大家介紹了Python自定義計時函數,希望對大家有所幫助,如果想要了解更多Python相關知識,請關注IT培訓機構:千鋒教育。http://www.fengjieshuijing.cn/