目录
1. 概要
本文介绍基于python内建模块timeit评估python小代码块的运行时间的方法。
2. Why timeit?
用time模块可以进行时间评估,在要执行的代码之前和之后分别用time.time()将时间记录下来,然后相减就得到了待测对象代码块的运行时间。但是这种方法有一些小的瑕疵不是很精确,有以下几方面的原因:
- 它不能正确处理系统时间发生复位的情况(当然这个在评估小的代码块的时间时可能无关紧要,而且(time.monotonic()可以解决这个问题)
- 它统计的时间可能包含一些背景进程的运行时间,因为它是看系统时间而不是针对待测代码块的CPU使用时间进行检测(time.perf_counter()解决了这个问题)
- 它只是统计运行一次的时间,因而必然会有可能出现较大的波动。当然你可以写个loop去重复执行多次。。。
Timeit解决了(3)的统计性偏差的问题,Timeit可以自动地进行大量次数的代码执行以获得更加精确的统计平均值。由于是进行了大量重复运行,因此由(2)所带来的背景进程运行所导致的干扰波动也会得到相应的抑制。所以Timeit用于对很小的代码片段(code snippet)进行运行时间评估非常方便。
以下介绍timeit模块中的主要的几个工具,以及命令行运行的方式。
3. timeit.timeit()
调用方式:timeit.timeit(stmt, setup, timer, number)
Stmt: 待评估对象,缺省值为’pass’
Setup: 这是指定在执行stmt之前运行的代码,缺省值为pass。可以用于比如说导入运行stmt所需要的库模块等
Timer: 指定一个timeit.Timer 对象。它通常有合理的缺省值,用户可以不管它的设置.
Number: 指定重复运行次数,缺省值为1000000.
返回值是秒单位的时间值。
3.1 Example1
让我们看一个简单的例子。
# importing the required module
import timeit
# Example1
# code snippet whose execution time is to be measured
mycode1 = '''
mylist = list(range(1000))
for k in range(1000):
if k in mylist:
pass
'''
mycode2 = '''
myset = set(range(1000))
for k in range(1000):
if k in myset:
pass
'''
mycode3 = '''
mydict = dict.fromkeys(list(range(1000)),'')
for k in range(1000):
if k in mydict:
pass
'''
# timeit statement
print('tCost for mycode1: ', timeit.timeit(stmt = mycode1, number = 1000) )
print('tCost for mycode2: ', timeit.timeit(stmt = mycode2, number = 1000) )
print('tCost for mycode3: ', timeit.timeit(stmt = mycode3, number = 1000) )
运行结果:
tCost for mycode1: 5.059251000002405
tCost for mycode2: 0.062394699998549186
tCost for mycode3: 0.07763279999926453
以上这个例子比较了python中list, dict, set的查询性能,从结果可以看出,dict和set查询性能相当,比list则要快2个数量级。
3.2 Example2
再看一个利用setup的例子。
# Example2
mysetup1 = "from math import sqrt"
mycode1 = '''
mylist = []
for k in range(1000):
mylist.append(sqrt(k))
'''
mysetup2 = "import numpy as np"
mycode2 = '''
mylist = []
for k in range(1000):
mylist.append(np.sqrt(k))
'''
mysetup3 = "import numpy as np"
mycode3 = '''
myrslt = np.sqrt(np.array(list(range(1000))))
'''
print('tCost for mycode1: ', timeit.timeit(setup = mysetup1, stmt = mycode1, number = 1000) )
print('tCost for mycode2: ', timeit.timeit(setup = mysetup2, stmt = mycode2, number = 1000) )
print('tCost for mycode3: ', timeit.timeit(setup = mysetup3, stmt = mycode3, number = 1000) )
运行结果:
tCost for mycode1: 0.12879980000070645
tCost for mycode2: 0.7638708999984374
tCost for mycode3: 0.05968639999991865
这个例子比较了python内置的math.sqrt()函数与numpy.sqrt()的运行速度差异。
令人意外的是,以标量形式进行运算的话,numpy.sqrt()竟然比math.sqrt()还要慢很多!此种必有蹊跷,回头再另行调查。
4. timeit.repeat()
调用方式:timeit.repeat(stmt, setup, timer, number, repeat)
Timeit.repeat()在timeit.timeit()的基础上又追加了一个repeat参数,用于指定重复次数。
老实讲,不明白为什么?把number*repeat设定给number然后调用timeit.timeit()有什么不好吗?不过,当然必须相信库开发者这样设计总是有它的道理的。搜了一艘,查到这么一个帖子:
what is diffrence between number and repeat in python timeit? - Stack Overflow
虽然隐约觉得明白了一些,但是还是不足以以自己的语言来解释一番,就不“为赋新词强说愁”了,等理解透彻并且自信能够解释清楚后再回来补课。先看例子吧:
# Example3, timeit.repeat()
print('\nExample3: ...')
mysetup3 = '''
from math import sqrt
def example3(N):
mylist = []
for k in range(N):
mylist.append(sqrt(k))
'''
mycode3 = '''
example3(1000)
'''
print('tCost for mycode3: ', timeit.repeat(setup = mysetup3, stmt = mycode3, number = 1000, repeat=100) )
运行结果:
Example3: ...
tCost for mycode3: [0.12954869999884977, 0.13357629999882192, 0.1325503000007302, 0.1383346000002348, 0.13686260000031325, 0.1361642000010761, 0.13500720000229194, 0.1356378999989829, 0.13645919999908074, 0.1313538000031258]
与timeit.timeit只给出一个结果不同的是,timeit.repeat()将每次repeat看作是一次独立的统计,总共给出的repeat指定次数的结果。基于这repeat个样本数据可以方便进行更深入的统计分析,比如说最大值、最小值、均值、标准差、直方图等等等等blabla。。。(超出了本文的范围,就此打住)
以上例子中还顺便还给出将待评估的代码(如果多行的话)封装为一个函数,从setup参数中导入,然后在stmt中值导入对函数的调用,提供了更进一步的灵活性。
5. 命令行使用
比如说,在AnacondaPrompt(当然其它命令行终端应该也是一样,只要能运行python就行)执行以下命令:
python -m timeit -s "from math import sqrt" -n 10000 -r 10 'x=sqrt(12345678)'
其中,-m是用于指定模块名,-s, -n, -r则分别对应timeit.repeat()函数的setup, number和repeat参数,最后的’’中的代码即是对应stmt参数的指定内容了。
此外在IPython环境下还可以用一下魔术命令的方式调用timeit:
import numpy as np
%timeit np.sqrt(123456789)
运行结果:
705 ns ± 2.66 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
另外关于python中的time模块的使用方法,请参考: Python运行时间评估: time modulehttps://blog.csdn.net/chenxy_bwave/article/details/119458690