详解timeit模块的使用

Last Updated: 2023-07-26 02:28:48 Wednesday

-- TOC --

Python自带的timeit模块,可以非常方便的用来测试一小段代码的运行效率。

命令行使用timeit

$ python3 -m timeit '[str(n) for n in range(1000)]'
5000 loops, best of 5: 79.8 usec per loop

用timeit测试一个list-comprehension,输出的含义为:每次执行5000次(loop)这段代码,共执行5次(repeat),最好的1次平均执行效率为79.8 usec(usec,微秒)。

timeit会自动测算每次适合执行多少个loop,以便控制整体测试时间,不会太久,也不会太快。如果我们稍微修改一下代码:

$ python3 -m timeit '[str(n) for n in range(10000)]'
500 loops, best of 5: 920 usec per loop

代码的循环从1000变成10000,timeit每次的测试就减少到了500个loop。因此不用太担心loop太多,测试时间太少,这个细节由timeit模块动态调整。

当然,我们也可以自己手动设定loop和repeat的数量,分别使用 -n-r 参数:

$ python3 -m timeit -n 1000 -r 10 '[str(n) for n in range(5000)]'
1000 loops, best of 10: 429 usec per loop

以上示例,即timeit的默认情况,我们得到的每个loop时间,是wallclock时间,即实际经过的时间,CPU在这段时间干的其它事情,比如中断,任务切换等等,都算在里面了,这样的时间,显然是不够准确!(Python中的time.perf_counter对应的是Python进程启动后经过的时间,也就是wallclock)如果我们需要更准确的代码执行时间,需要加上 -p 参数,process time,这样得到的时间,是CPU专门用在测试代码上的运行时间:

$ python3 -m timeit -p '[str(n) for n in range(50000)]'
100 loops, best of 3: 8.08 msec per loop

timeit还有一个 -u 参数,可以用来指定输出时间的单位(nsec, usec, msec, sec):

$ python3 -m timeit -p -u nsec '[str(n) for n in range(5000)]'
500 loops, best of 5: 4.08e+05 nsec per loop
$ python3 -m timeit -p -u usec '[str(n) for n in range(5000)]'
500 loops, best of 5: 405 usec per loop
$ python3 -m timeit -p -u msec '[str(n) for n in range(5000)]'
500 loops, best of 5: 0.405 msec per loop
$ python3 -m timeit -p -u sec '[str(n) for n in range(5000)]'
500 loops, best of 5: 0.000423 sec per loop

还有一个非常有用的 -s 参数,setup的意思,指定的启动代码,这些代码在测试期间,只运行一次,用来初始化测试环境,比如import一些模块或初始化变量等:

$ python3 -m timeit -s 'import random' -p '[random.randint(0,9) for i in range(1000)]'
500 loops, best of 5: 415 usec per loop

-v,verbose

$ python3 -m timeit -s 'import random' -p -v '[random.randint(0,9) for i in ra
nge(1000)]'
1 loop -> 0.000396 secs
2 loops -> 0.000832 secs
5 loops -> 0.00208 secs
10 loops -> 0.00422 secs
20 loops -> 0.00822 secs
50 loops -> 0.0209 secs
100 loops -> 0.0425 secs
200 loops -> 0.083 secs
500 loops -> 0.206 secs

raw times: 212 msec, 204 msec, 206 msec, 209 msec, 206 msec

500 loops, best of 5: 408 usec per loop

代码中使用timeit

>>> from timeit import timeit
>>> timeit('[str(n) for n in range(1000)]')
82.6349102639997

执行了82秒,有点长了。因为代码中的这个timeit接口默认将测试代码循环100万次!我们需要自己指定循环的次数,并可自己做个除法,得到平均执行时间:

>>> timeit('[str(n) for n in range(1000)]',number=10000) / 10000
8.695862459999262e-05
>>> timeit('[str(n) for n in range(1000)]',number=50000) / 50000
8.978061499999967e-05

再看看repeat接口:

>>> from timeit import repeat
>>> repeat('[str(n) for n in range(1000)]',number=1000,repeat=2)
[0.08929291000004014, 0.07842608700048004]

多次repeat的时间(not per loop),形成一个list返回。

timeit模块的函数接口,默认是使用wallclock(time.perf_counter),如何切换到process time呢?如下:

>>> import time
>>> timeit('[str(n) for n in range(1000)]', number=10000, timer=time.process_time)
>>> repeat('[str(n) for n in range(1000)]', timer=process_time, number=1000, repeat=2)

当测试某个函数接口时,需要使用globals参数传递对象,timeit的执行环境,是一个独立的namespace,globals相当于定义timeit执行环境的全局对象。对于使用到的全局变量,还需要额外的global申明:

from timeit import timeit

sr = {}
def test_function():
    global sr
    ...

timeit('global sr; sr={};test_function()',...,globals=globals())

global sr的申明是必须的!

本文链接:https://cs.pynote.net/sf/python/202111026/

-- EOF --

-- MORE --