27.5. timeit
- 测量小代码片段的执行时间¶
源代码: Lib / timeit.py
本模块提供一个简单的方法来测量小的Python代码的时间。它同时具有Command-Line Interface以及callable的函数。它避免了测量执行时间时大量的常见陷阱。另请参阅O'Reilly出版的Python Cookbook一书中,由Tim Peters编写的"算法"简介一章。
27.5.1. Basic Examples¶
下面的示例演示如何使用Command-Line Interface比较三种不同的表达式:
$ python3 -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 3: 30.2 usec per loop
$ python3 -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 3: 27.5 usec per loop
$ python3 -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 3: 23.2 usec per loop
这也可以通过Python Interface实现:
>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.3018611848820001
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.2727368790656328
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.23702679807320237
27.5.2. Python Interface¶
该模块定义了三个便利的函数和用一个公共类:
-
timeit.
timeit
(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)¶ Create a
Timer
instance with the given statement, setup code and timer function and run itstimeit()
method with number executions. 可选的全局变量参数指定在其中执行代码的命名空间。在版本3.5中已更改:添加了可选的全局参数。
-
timeit.
repeat
(stmt='pass', setup='pass', timer=<default timer>, repeat=3, number=1000000, globals=None)¶ Create a
Timer
instance with the given statement, setup code and timer function and run itsrepeat()
method with the given repeat count and number executions. 可选的全局变量参数指定在其中执行代码的命名空间。在版本3.5中已更改:添加了可选的全局参数。
-
timeit.
default_timer
()¶ 默认计时器,始终为
time.perf_counter()
。在版本3.3中更改:
time.perf_counter()
现在是默认计时器。
- class
timeit.
Timer
(stmt='pass', setup='pass', timer=<timer function>, globals=None)¶ 用于计时小代码段执行速度的类。
该构造函数接受要计时的语句,以及一个额外的用于初始化的语句和一个计时器函数。两个语句默认为
'pass'
;定时器功能是平台相关的(参见模块doc字符串)。stmt和设置也可能包含多个由;
分隔的语句或换行符,只要它们不包含多行字符串字面值即可。语句将默认在timeit的命名空间中执行;可以通过将命名空间传递到全局变量来控制此行为。若要测量的第一个语句的执行时间,请使用
timeit()
方法。repeat()
方法是多次调用timeit()
的一种便利方法,并返回结果的列表。设置的执行时间从整个定时执行运行中排除。
stmt和设置参数也可以使用无参数可调用的对象。这将在随后由
timeit()
执行的定时器函数中嵌入对它们的调用。请注意在这种情况下由于额外的函数调用时间开销会有点大。在版本3.5中已更改:添加了可选的全局参数。
-
timeit
(number=1000000)¶ 计时主要语句执行number次的时间。它将执行一次setup语句,返回执行主要语句执行多次所需的时间,以浮点数秒数表示。参数为循环的次数,默认是100万。要用的主语句、setup语句和计时器函数将传递给构造函数。
注
默认情况下,在计时期间
timeit()
暂时关闭garbage collection。这种方法的优点是它使得独立的计时更具有可比性。缺点是垃圾回收可能被测量函数性能的一个重要组成部分。如果是这样,垃圾回收可以利用setup字符串的第一个语句重新启用。举个例子:timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
-
repeat
(repeat=3, number=1000000)¶ 调用
timeit()
多次。这是一个方便的函数重复调用
timeit()
,并返回结果的列表。第一个参数指定调用timeit()
多少次。第二个参数指定timeit()
的number参数。注
从结果向量它很容易计算平均值和标准偏差,并报告这些。然而,这不是很有用的。在典型情况下,最低值给出了您的机器运行给定代码片段的速度的下限;结果向量中较高的值通常不是由Python的速度变化引起的,而是由其他进程干扰您的时序精度造成的。所以结果中的
min()
可能你应该感兴趣的唯一一个数。在那之后,你应该看看整个矢量并应用常识而不是统计数据。
-
print_exc
(file=None)¶ 从被计时的代码打印回溯的helper。
典型的使用:
t = Timer(...) # outside the try/except try: t.timeit(...) # or t.repeat(...) except Exception: t.print_exc()
相比标准的回溯的优点是将显示编译后的模板中的源代码行。可选的文件参数指示发送回溯的位置;它默认为
sys.stderr
。
-
27.5.3. Command-Line Interface¶
当作为一个程序从命令行调用时,使用以下格式:
python -m timeit [-n N] [-r N] [-u U] [-s S] [-t] [-c] [-h] [statement ...]
下面是选项的解释:
-
-n
N
,
--number
=N
¶ 执行'statement'多少次
-
-r
N
,
--repeat
=N
¶ 重复计时器多少次(默认为3)
-
-s
S
,
--setup
=S
¶ 一旦初识化后要执行的语句(默认为
pass
)
-
-p
,
--process
¶
测量处理时间,而不是时钟时间,使用
time.process_time()
而不是time.perf_counter()
版本3.3中的新功能。
-
-t
,
--time
¶
使用
time.time()
(已弃用)
-
-u
,
--unit
=U
¶ - specify a time unit for timer output; can select usec, msec, or sec
版本3.5中的新功能。
-
-c
,
--clock
¶
使用
time.clock()
(已弃用)
-
-v
,
--verbose
¶
打印原始时序结果;重复以获得更多数字精度
-
-h
,
--help
¶
打印一个短的用法信息并退出
多行语句可以通过将每一行指定为单独的语句参数来给出;缩进线可以通过使用引号包围参数并使用前导空格。多个-s
选项的处理方式类似。
如果不指定-n
,适当数目的循环计算的尝试连续的 10 的幂,直到总的时间为至少 0.2 秒。
default_timer()
测量可能会受到在同一台机器上运行的其他程序的影响,因此,当需要准确计时时,最好的做法是重复计时几次,并使用最佳时间。-r
选项对此有好处;在大多数情况下默认为3次重复可能就足够了。您可以使用time.process_time()
来测量CPU时间。
注
还有与执行 pass 语句相关联的某些基线系统开销。这里的代码不会尝试隐藏它,但是你应该意识到这一点。可以通过调用不带参数,程序测量基线开销和 Python 版本之间可能有差异。
27.5.4. Examples¶
可以提供在开始时只执行一次的setup语句:
$ python -m timeit -s 'text = "sample string"; char = "g"' 'char in text'
10000000 loops, best of 3: 0.0877 usec per loop
$ python -m timeit -s 'text = "sample string"; char = "g"' 'text.find(char)'
1000000 loops, best of 3: 0.342 usec per loop
>>> import timeit
>>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
1.7246671520006203
同样可以使用Timer
类和它的方法做到:
>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40193588800002544, 0.3960157959998014, 0.39594301399984033]
下面的示例演示如何计时包含多行的表达式。在这里我们比较使用hasattr()
与try
/except
来测试对象缺失和存在的属性:
$ python -m timeit 'try:' ' str.__bool__' 'except AttributeError:' ' pass'
100000 loops, best of 3: 15.7 usec per loop
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
100000 loops, best of 3: 4.26 usec per loop
$ python -m timeit 'try:' ' int.__bool__' 'except AttributeError:' ' pass'
1000000 loops, best of 3: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 3: 2.23 usec per loop
>>> import timeit
>>> # attribute is missing
>>> s = """\
... try:
... str.__bool__
... except AttributeError:
... pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.5829014980008651
>>>
>>> # attribute is present
>>> s = """\
... try:
... int.__bool__
... except AttributeError:
... pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.04215312199994514
>>> s = "if hasattr(int, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.08588060699912603
为了给timeit
模块访问给您定义的函数的权限,可以通过传递setup参数,其中包含import语句:
def test():
"""Stupid test function"""
L = [i for i in range(100)]
if __name__ == '__main__':
import timeit
print(timeit.timeit("test()", setup="from __main__ import test"))
另一个选项是将globals()
传递到全局参数,这将导致代码在当前全局命名空间中执行。这可以比单独指定导入更方便:
def f(x):
return x**2
def g(x):
return x**4
def h(x):
return x**8
import timeit
print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))