timer(计时器)
我们经常需要统计一块代码或一个函数运行的时间,下面总结一些计时方法,这些都来自于python内置的time模块。
time.time(): 这个函数返回一个浮点数,表示从起始时间点(1970-01-01, 00:00:00 (UTC)) 到现在的时间经过的秒数。
time.perf_counter(): 用于性能测量,返回一个浮点数,但不像time()函数返回的是真正的时间,它返回的是一个时间参考点。 两次调用该函数的时间参考点相减就是经过的时间。 它比time()计算的时间差精度更高,这是由浮点数的特性决定的,因为time()返回的浮点数过大,导致留给控制精度的数位不足。
time.process_time(): 也返回一个表示时间参考点的浮点数,用于统计当前进程的系统和用户 CPU 时间的总计值。 它不包括睡眠状态所消耗的时间。即不包括IO时间。
time.thread_time(): 与process_time方法类似,但是表示的是当前线程的CPU时间总计值。
此外,还有一些加了_ns的函数,表示精确到纳秒级。比如 time.perf_counter_ns(), time.process_time_ns()
import time
print('time():', time.time())
print('process_time():', time.process_time())
print('perf_counter():', time.perf_counter())
time.sleep(2)
print('perf_counter():', time.perf_counter())
print('process_time():', time.process_time())
print('time():', time.time())
输出结果:
time(): 1739797436.4068682
process_time(): 0.0625
perf_counter(): 35173.1468832
perf_counter(): 35175.1477271
process_time(): 0.0625
time(): 1739797438.4082105
一般我们都推荐使用time.perf_counter()。
最后,是python内置模块timeit,它提供了对于短代码的计时函数,可以在命令行里运行,在jupyter notebook中也很常用。
timeit.timeit(stmt='pass', setup='pass', timer=
stmt: 一段代码或一个callable对象。 如果是代码,需要用引号包裹起来,如果是一个callable,则该callable必须不用传参,比如无参函数。
setup: 一段代码,需用引号包裹,一般是用于执行代码需要的前置条件,比如import语句导入必要的模块。
timer: 计时器函数,一般不需要改,默认为timeit.default_timer,而它要源于time.perf_counter。
number: 对代码运行的次数,默认为一百万次。所以代码真正运行一次的时间 = 该函数返回的总时间 / number
globals: 用于传递运行代码时的变量命名空间,一般传globas(),这样就不需要setup参数里传import模块的语句了。
如下示例报错
import timeit
import math
sqrt_time = timeit.timeit(stmt="math.sqrt(100)",number=10000)
print(f"math.sqrt(100) 耗时: {sqrt_time} 秒")
输出结果:
NameError: name 'math' is not defined
原因是stmt里用到了math模块,虽然全局空间里已导入,但被执行的代码块里并不知道。
下面代码可以运行成功,即便全局空间中没有导入math模块,但函数setup参数里传递了'import math'。
import timeit
sqrt_time = timeit.timeit(stmt="math.sqrt(100)",setup='import math', number=10000)
print(f"math.sqrt(100) 耗时: {sqrt_time} 秒")
输出结果:
math.sqrt(100) 耗时: 0.001000799999019364 秒
下面代码使用globals参数,相当于把全局命名空间传给了函数,也能运行成功。
import timeit
import math
sqrt_time = timeit.timeit(stmt="math.sqrt(100)",number=10000, globals=globals())
print(f"math.sqrt(100) 耗时: {sqrt_time} 秒")
输出结果:
math.sqrt(100) 耗时: 0.000996599999780301 秒
下面演示一下stmt参数不传代码块,而传一个无参函数,这时就不用设置setup和globals参数了。
import timeit
import math
def fn():
return math.sqrt(100)
sqrt_time = timeit.timeit(stmt=fn,number=10000)
print(f"math.sqrt(100) 耗时: {sqrt_time} 秒")
输出结果:
math.sqrt(100) 耗时: 0.001350400001683738 秒
timeit.repeat(stmt='pass', setup='pass', timer=
import timeit
import math
sqrt_time = timeit.repeat(stmt="math.sqrt(100)",repeat=5, number=10000, globals=globals())
print(f"math.sqrt(100) 耗时: {sqrt_time} 秒")
输出结果:
math.sqrt(100) 耗时: [0.000997399998595938, 0.0009960999996110331, 0.00102100000003702, 0.0009858000012172852, 0.0009867999979178421] 秒
可以看到,repeat函数输出的结果是一个列表,代表重复执行了几次timeit函数所用的时间。便于我们求平均值。
class timeit.Timer(stmt='pass', setup='pass', timer=
下面演示了一下先创建Timer实例,然后调用autorange方法。
import timeit
import math
timer = timeit.Timer(stmt="math.sqrt(100)",globals=globals())
result = timer.autorange()
print(result)
输出结果:
(5000000, 0.4683586999999534)
从输出结果可以看到,当传入number=5000000,执行时间超过了0.2秒。
autorange方法还支持传入一个回调函数,需要带有两个参数(number, time_taken),下面演示一下用法。
import timeit
import math
timer = timeit.Timer(stmt="math.sqrt(100)",globals=globals())
def my_callback(number, time_taken):
print(f"number={number}, 总执行时间={time_taken:.6f} 秒")
result = timer.autorange(callback=my_callback)
print(result)
sqrt_time = timer.timeit(number=5000000)
print(sqrt_time)
输出结果:
(5000000, 0.4683586999999534)
number=1, 总执行时间=0.000001 秒
number=2, 总执行时间=0.000000 秒
number=5, 总执行时间=0.000001 秒
number=10, 总执行时间=0.000001 秒
number=20, 总执行时间=0.000002 秒
number=50, 总执行时间=0.000006 秒
number=100, 总执行时间=0.000010 秒
number=200, 总执行时间=0.000026 秒
number=500, 总执行时间=0.000062 秒
number=1000, 总执行时间=0.000091 秒
number=2000, 总执行时间=0.000192 秒
number=5000, 总执行时间=0.000465 秒
number=10000, 总执行时间=0.000938 秒
number=20000, 总执行时间=0.001923 秒
number=50000, 总执行时间=0.004937 秒
number=100000, 总执行时间=0.009330 秒
number=200000, 总执行时间=0.018733 秒
number=500000, 总执行时间=0.046860 秒
number=1000000, 总执行时间=0.093587 秒
number=2000000, 总执行时间=0.191843 秒
number=5000000, 总执行时间=0.479606 秒
(5000000, 0.47960579999926267)
0.4838593000022229
输出结果中可以看到,autorange方法不断尝试翻倍传给number参数的值,当number=2000000时,执行时间为0.19秒,仍小于0.2秒,所以继续扩大number值。
最后我们执行timer.timeit(number=5000000),直接传递number=5000000),得到的结果为0.4838593000022229,结果大致相等。
上面各种计时方法在使用时,还可以根据实际情况改变timer参数,比如改成time.process_time, 这样就可以只计算cpu时间,而不计算IO时间了。

浙公网安备 33010602011771号