发布时间:2022-04-22 10:02:41 人气:294 作者:多测师
Python标准库提供的cProfile/profile模块,计时输出信息较多。本节将介绍其他几种精度略低但简单易用的计时工具。根据代码粒度不同,将其分为三类。
1.1 整个程序计时
Unix/Linux系统中,可用time命令简单地统计整个程序的耗时。例如:
[wangxiaoyuan_@localhost PyTest]$ time python BCLineCounter.py bulk
FileLines CodeLines CommentLines EmptyLines CommentPercent
15450 10437 3264 2538 0.24
real 0m2.803s
user 0m1.124s
sys 0m0.052s
统计值real表示程序运行的实际耗时,user表示程序执行用户态代码(内核外)耗费的CPU时间,sys表示程序在内核态运行所耗费的CPU时间(即调用特定内核函数的耗时)。若user和sys时间之和小于real时间,表明程序为I/O密集型(I/O bound),即程序的性能问题很可能与等待I/O有关。
time命令的详细描述参见《Linux用户态程序计时方式详解》。
1.2 代码片段计时
代码片段计时分为函数计时和语句块计时。这两种计时均可使用Python标准库timeit模块,该模块的详细介绍参见官方帮助。
本小节将使用timeit模块的timeit()方法,即timeit(stmt='pass', setup='pass', timer=, number=1000000)。其中,参数stmt为待计时的目标代码;setup为执行代码的准备工作(通常是import之类的语句),不计入时间;timer在Windows系统中为time.clock(),Linux系统中则为time.time(),取默认值即可;number指示stmt重复执行的次数。该方法返回执行stmt代码number遍所用的时间,单位为秒,float类型。
除timeit()方法外,对于特定函数的计时,可使用装饰器(decorator);对于语句块计时,则可使用上下文管理器(context manager)。
以装饰器为例:
import functools, sys, time
def FuncTimer(repeats=10000):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
#Windows系统中clock()粒度为毫秒,time()粒度为1/60秒;
#Unix系统中clock()粒度为1/100秒,time()精度较其更高。
if sys.platform == "win32":
timerFunc = time.clock
else:
timerFunc = time.time
try:
startTime = timerFunc()
for i in range(repeats):
ret = func(*args, **kwargs)
finally: #当目标函数发生异常时,仍旧输出计时信息
endTime = timerFunc()
print '%s.%s() =>' %(func.__module__, func.__name__),
print 'Time Elasped: %.3f msec, repeated %d time(s).' \
%(((endTime-startTime)*1000.0), repeats)
return ret
return wrapper
return decorator
运行如下代码,对比自定义装饰器FuncTimer与timeit模块计时效果:
@FuncTimer(10)
def DecoratedFunc():
L = []
for i in range(100): L.append(i)
def RawFunc():
L = []
for i in range(100): L.append(i)
DecoratedFunc()
import timeit; print '%.6f sec' %timeit.timeit(stmt=RawFunc, number=10)
输出如下:
__main__.DecoratedFunc() => Time Elasped: 0.164 msec, repeated 10 time(s).
0.000174 sec
可见,计时效果非常接近。
注意,FuncTimer装饰器内根据系统选用不同的计时器,这是考虑到time.clock()的精度因系统平台而异。在Unix/Linux系统中,该方法返回当前所耗的CPU时间;而在Windows系统中,该方法基于Win32函数 QueryPerformanceCounter(),返回从首次调用待计时函数起所经历的挂钟时间(wall clock time),精度较time.time()更高。相比而言,timeit方法中使用的缺省计时器总是测量挂钟时间,这也意味着关于某函数的计时可能会受到同一计算机上运行的其他进程的影响。
time.clock()计时器的平台差异性参考以下示例(假定所在脚本名为Timing.py):
@FuncTimer(5)
def SqrtTiming(loops):
import math
try:
from math import fsum #Python2.6+
return fsum([math.sqrt(x) for x in range(loops)])
except ImportError: #Python2.5-
return sum([math.sqrt(x) for x in range(loops)])
@FuncTimer(1)
def SleepTiming():
time.sleep(2)
file = open(r'out.txt', "w+")
for i in range(10000):
file.write('hello world!')
SqrtTiming(100000)
SleepTiming()
在Windows系统控制台和IDLE Shell里的运行结果如下:
E:\PyTest>Timing.py
SqrtTiming() => Time Elasped: 150.124 msec, repeated 5 time(s).
SleepTiming() => Time Elasped: 2155.140 msec, repeated 1 time(s).
__main__.SqrtTiming() => Time Elasped: 151.809 msec, repeated 5 time(s).
__main__.SleepTiming() => Time Elasped: 2185.594 msec, repeated 1 time(s).
>>> import Timing
Timing.SqrtTiming() => Time Elasped: 148.892 msec, repeated 5 time(s).
Timing.SleepTiming() => Time Elasped: 2223.157 msec, repeated 1 time(s).
在Linux系统中运行结果与之类似。若将timerFunc改为time.clock(),则计时输出为:
[wangxiaoyuan_@localhost ~]$ time python Timing.py
__main__.SqrtTiming() => Time Elasped: 320.000 msec, repeated 5 time(s).
__main__.SleepTiming() => Time Elasped: 330.000 msec, repeated 1 time(s).
real 0m2.381s
user 0m0.332s
sys 0m0.019s
可见,time.sleep(2)并未计入SleepTiming()耗时,导致计时结果与real时间相差很大。
对于代码片段计时,以上下文管理器为例:
import contextlib, sys, time
@contextlib.contextmanager
def BlockTimer(label='Block'):
if sys.platform == "win32": timerFunc = time.clock
else: timerFunc = time.time
startTime = timerFunc()
try:
yield
finally:
endTime = timerFunc()
print '%s =>' %label,
print 'Time Elasped: %.3f msec.' \
%((endTime-startTime)*1000.0)
基于BlockTimer测量代码片段的示例如下:
with BlockTimer('cPickle'):
from cPickle import dumps, loads
s = dumps([x*2.4 for x in range(100000)])
loads(s)
with BlockTimer('json'):
from json import dumps, loads
s = dumps([x*2.4 for x in range(100000)])
loads(s)
运行结果如下:
cPickle => Time Elasped: 237.569 msec.
json => Time Elasped: 181.714 msec.
可见,对于浮点型对象,json模块执行速度比cPickle模块更快。
当然,借助timeit模块也可对代码片段计时。例如:
from timeit import timeit
sep = 'from cPickle import dumps, loads'
stp = 's=dumps([x*2 for x in range(100000)]); loads(s)'
print 'cPickle: %.6f sec' %timeit(stmt=stp, setup=sep, number=1)
sej = 'from json import dumps, loads'
stj = 's=dumps([x*2 for x in range(100000)]); loads(s)'
print 'json: %.6f sec' %timeit(stmt=stj, setup=sej, number=1)
本例改为整型对象,且模块导入语句不计入总耗时。运行结果如下:
cPickle: 0.100775 sec
json: 0.064752 sec
可见,对于整型对象,json模块执行速度也比cPickle模块快。
以上内容为大家介绍了Python自定义计时函数,希望对大家有所帮助,如果想要了解更多Python相关知识,请关注多测师。https://www.e70w.com/xwzx/