Skip to main content

27.5. timeit —测量小代码片段的执行时间

源代码: Lib/timeit.py


这个模块提供了一种简单的方法来计算小代码的Python代码。它既有 命令行界面 也有 可调。它避免了许多用于测量执行时间的常见陷阱。另见Tim Peters对 Python Cookbook 中“算法”一章的介绍,由O’Reilly出版。

27.5.1. 基本示例

以下示例显示了如何使用 命令行界面 来比较三种不同的表达式:

$ 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接口 实现:

>>> 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 才会自动确定重复次数。在 例子 部分中,您可以找到更多高级示例。

27.5.2. Python接口

模块定义了三个方便的函数和一个公共类:

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

使用给定语句,setup 代码和 timer 函数创建 Timer 实例,并使用 number 执行运行其 timeit() 方法。可选的 globals 参数指定要在其中执行代码的命名空间。

在 3.5 版更改: 添加了可选的 globals 参数。

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=3, number=1000000, globals=None)

使用给定的语句,setup 代码和 timer 函数创建 Timer 实例,并使用给定的 repeat 计数和 number 执行运行其 repeat() 方法。可选的 globals 参数指定在其中执行代码的命名空间。

在 3.5 版更改: 添加了可选的 globals 参数。

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字符串)。 stmtsetup 也可以包含由 ; 或换行符分隔的多个语句,只要它们不包含多行字符串文字。语句将默认在timeit的命名空间内执行;这个行为可以通过传递一个命名空间到 globals 来控制。

要测量第一个语句的执行时间,请使用 timeit() 方法。 repeat()autorange() 方法是多次调用 timeit() 的方便方法。

setup 的执行时间从整个定时执行运行中排除。

stmtsetup 参数也可以获取可调用的对象,不带参数。这将在定时器函数中嵌入对它们的调用,然后由 timeit() 执行。注意,在这种情况下,由于额外的函数调用,定时开销稍大。

在 3.5 版更改: 添加了可选的 globals 参数。

timeit(number=1000000)

时间 number 执行的主要语句。这会执行一次setup语句,然后返回执行main语句所需的时间,以秒为单位测量为float。参数是循环的次数,默认为一百万。将要使用的主语句,setup语句和定时器函数传递给构造函数。

注解

默认情况下,timeit() 在计时期间临时关闭 garbage collection。这种方法的优点是它使独立的时序更可比。这个缺点是GC可能是被测功能的重要组成部分。如果是这样,GC可以重新启用作为 setup 字符串中的第一个语句。例如:

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
autorange(callback=None)

自动确定调用 timeit() 的次数。

这是一个方便的函数,重复调用 timeit(),使总时间> = 0.2秒,返回最终(循环次数,循环次数)。它调用 timeit(),其中 number 设置为十(10,100,1000,...)的连续幂,直到最大为十亿,直到所花费的时间为至少0.2秒,或者达到最大值。

如果给出 callback 并且不是 None,在每次试验后将调用两个参数:callback(number, time_taken)

3.6 新版功能.

repeat(repeat=3, number=1000000)

调用 timeit() 几次。

这是一个方便的函数,重复调用 timeit(),返回结果列表。第一个参数指定调用 timeit() 的次数。第二个参数指定 timeit()number 参数。

注解

很容易从结果向量计算平均值和标准偏差,并报告这些。但是,这不是很有用。在典型情况下,最低值给出了您的机器运行给定代码片段的速度的下限;结果向量中较高的值通常不是由Python的速度变化引起的,而是由其他进程干扰您的时序精度造成的。所以 min() 的结果可能是唯一你应该感兴趣的数字。之后,你应该看看整个向量和应用常识而不是统计。

print_exc(file=None)

帮助程序从定时代码打印回溯。

典型用途:

t = Timer(...)       # outside the try/except
try:
    t.timeit(...)    # or t.repeat(...)
except Exception:
    t.print_exc()

标准回溯的优点是将显示编译模板中的源行。可选的 file 参数指示发送回溯的位置;它默认为 sys.stderr

27.5.3. 命令行界面

当从命令行调用作为程序时,使用以下形式:

python -m timeit [-n N] [-r N] [-u U] [-s S] [-t] [-c] [-h] [statement ...]

其中理解以下选项:

-n N, --number=N

执行“语句”的次数

-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

指定定时器输出的时间单位;可以选择usec,msec或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. 例子

可以提供在开始时只执行一次的设置语句:

$ 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 模块访问您定义的函数,可以传递一个包含import语句的 setup 参数:

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() 传递给 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()))