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

请注意,只有使用命令行接口时,timeit才会自动确定循环的次数。Examples一节中可以找到更高级的例子。

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 its timeit() 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 its repeat() 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()))