python standard library timeit

python 标准库 timeit

每日一词:

Pneumonia

n 肺炎

2019 Novel coronavirus (2019-nCoV),
世界卫生组织定义, 又名武汉冠状病毒(Wuhan coronavirus)、武汉肺炎(Wuhan pneumonia)

冠状病毒属的病毒是具外套膜(envelope)

例句:

The doctor diagnosed the illness as pneumonia .
医生诊断这病为肺炎。

The doctor has cured her of pneumonia .
大夫把她的肺炎看好了。

Fatalities usually come from pneumonia .
通常肺炎可引起死亡。

源码

源码: Lib/timeit.py

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

基本实例

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

1
2
3
4
5
6
$ python3 -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 5: 30.2 usec per loop
$ python3 -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 5: 27.5 usec per loop
$ python3 -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 5: 23.2 usec per loop

这可以通过 Python 接口 实现

1
2
3
4
5
6
7
>>> 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

Python 接口 还可以传出一个可调用对象:

1
2
>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678

但请注意 timeit() 仅在使用命令行界面时会自动确定重复次数。 在 示例 一节你可以找到更多的进阶示例。

python接口

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

  • timeit.timeit(stmt=’pass’, setup=’pass’, timer=, number=1000000, globals=None)
  • timeit.repeat(stmt=’pass’, setup=’pass’, timer=, repeat=5, number=1000000, globals=None)
  • timeit.default_timer()
  • class timeit.Timer(stmt=’pass’, setup=’pass’, timer=, globals=None)
    • timeit(number=1000000)
    • autorange(callback=None)
    • repeat(repeat=5, number=1000000)
    • print_exc(file=None)

命令行界面

从命令行调用程序时,使用以下表单:

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

如果了解以下选项:

  • -n N, --number=N

    执行 ‘语句’ 多少次

  • -r N, --repeat=N

    重复计时器的次数(默认为5)

  • -s S, --setup=S

    最初要执行一次的语句(默认为 pass

  • `-p, –process

    1
    2
    3
    4
    5
    6
    7
    8

    测量进程时间,而不是 wallclock 时间,使用 [`time.process_time()`](https://docs.python.org/zh-cn/3.8/library/time.html#time.process_time) 而不是 [`time.perf_counter()`](https://docs.python.org/zh-cn/3.8/library/time.html#time.perf_counter) ,这是默认值*3.3 新版功能.*

    - `-u````, ``--unit``=U`

    指定定时器输出的时间单位;可以选择 nsec,usec,msec或sec*3.5 新版功能.*

    - `-v````, ``--verbose

    打印原始计时结果;重复更多位数精度

  • -h```, --help`

    打印一条简短的使用信息并退出

可以通过将每一行指定为单独的语句参数来给出多行语句;通过在引号中包含参数并使用前导空格可以缩进行。多个 -s 选项的处理方式相似。

如果 -n 未给出,则通过尝试10的连续幂次来计算合适数量的循环,直到总时间至少为 0.2 秒。

default_timer() 测量可能受到在同一台机器上运行的其他程序的影响,因此在需要精确计时时最好的做法是重复几次计时并使用最佳时间。 -r 选项对此有利;在大多数情况下,默认的 5 次重复可能就足够了。 你可以使用 time.process_time() 来测量CPU时间。

执行 pass 语句会产生一定的基线开销。这里的代码不会试图隐藏它,但你应该知道它。可以通过不带参数调用程序来测量基线开销,并且Python版本之间可能会有所不同。

实例

可以提供一个在开头只执行一次的 setup 语句:

1
2
3
4
$ python -m timeit -s 'text = "sample string"; char = "g"'  'char in text'
5000000 loops, best of 5: 0.0877 usec per loop
$ python -m timeit -s 'text = "sample string"; char = "g"' 'text.find(char)'
1000000 loops, best of 5: 0.342 usec per loop
1
2
3
4
5
>>> 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 类及其方法可以完成同样的操作:

1
2
3
4
5
6
>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

以下示例显示如何计算包含多行的表达式。 在这里我们对比使用 hasattr()try/except 的开销来测试缺失与提供对象属性:

1
2
3
4
5
6
7
8
9
$ python -m timeit 'try:' '  str.__bool__' 'except AttributeError:' '  pass'
20000 loops, best of 5: 15.7 usec per loop
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
50000 loops, best of 5: 4.26 usec per loop

$ python -m timeit 'try:' ' int.__bool__' 'except AttributeError:' ' pass'
200000 loops, best of 5: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 5: 2.23 usec per loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> 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 参数:

1
2
3
4
5
6
7
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 参数,这将导致代码在当前的全局命名空间中执行。这比单独指定 import 更方便

1
2
3
4
5
6
7
8
9
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()))
坚持原创技术分享,您的支持将鼓励我继续创作!